为什么在redux中需要'Actions'作为数据?

时间:2016-01-13 05:30:01

标签: javascript typescript redux

Redux documentations说我应该制作动作和动作创作者,如下所示:

function addTodo(filter) {
  return { 
    type: SET_VISIBILITY_FILTER, 
    filter
  }
}

然后写减速器,如下:

function todoApp(state = initialState, action) {
  switch (action.type) {
   case SET_VISIBILITY_FILTER:
     return Object.assign({}, state, {
       visibilityFilter: action.filter
     });
  } 
}

然后我使用dispatch调用该操作:

store.dispatch(addTodo("Ask question on stackoverflow"));

似乎行动和减速器之间存在一对一的对应关系;该操作的唯一目的是选择减速器并为该减速器提供输入数据。

为什么我们不跳过中间人并确定减速器和具有减速器功能的动作创建器的动作?然后dispatch将采用单个参数,类型为State => State的缩减器/操作:

// Action/reducer. (Parametrised state transformer, really.)
const addTodo = text => state => {
  return Object.assign({}, state, {
       visibilityFilter: action.filter
  });
}

// Dispatch takes as a argument the action/reducer
store.dispatch(addTodo("Ask question on stackoverflow"));

你将失去序列化操作的能力,但除此之外,似乎你摆脱了样板操作创建者并更清楚地表达了动作和减速器之间的联系。如果您使用的是Typescript,那么您还可以对操作中的数据进行类型检查,即difficult to express otherwise

那么我错过了将数据作为数据的原因是什么?

3 个答案:

答案 0 :(得分:5)

Redux中操作的主要目的是减少状态。 将在动作数组上调用Reduce方法(这就是为什么它称为 reducer )。例如:

import reducer from './reducer';

const actions = [
    {type: 'INIT'},
    {type: 'SOME_ACTION', params: {...}},
    {type: 'RECEIVE_DATA', data: [...]},
    {type: 'SOME_ANOTHER_ACTION', params: {...}},
    {type: 'RECEIVE_DATA', data: [...]},
    ...
];

const finalState = actions.reduce(reducer, undefined);

动作创建者是一种可以创建动作的功能。 动作创建者不一定只创建一个动作。

实际上,如果你的reducer能够接收函数而不是对象 - 你的动作将是函数,它将完成主要目的,但你可以放弃Redux功能的一些好处。

在这种情况下,reducer将实现如下:

function reducer(state, action) {
    return action(state);
}

您可以以{type: 'ACTION_NAME'}格式创建操作的原因:

  1. Redux DevTools 期望这种格式。
  2. 您需要存储一系列操作。
  3. Reducer对worker进行状态转换。
  4. Redux生态系统中的每个人都使用这种格式。这是一种惯例。
  5. 热重装功能(不会重新加载存储的功能)。
  6. 您需要按原样在服务器上发送操作。
  7. 调试好处 - 查看具有操作名称的操作堆栈。
  8. 为reducer编写单元测试:assert.equal(finalState, expectedState)
  9. 更多声明性代码 - 操作名称和参数是关于“做什么”而不是“怎么做”(但addTodo('Ask question')也是声明性的)。
  10. 请注意动作创建者与状态变化之间的耦合

    只需比较两种符号:

    第一:

    function someActionCreator() {
        return {
            type: "ADD_TODO",
            text: "Ask question on stackoverflow"
        }; // returns object
    }
    

    第二

    function someActionCreator() {
        return addTodo("Ask question on stackoverflow"); // returns function
    }
    

    “在这两种情况下,我们都看到代码是声明性的,并且动作创建者与状态更改分离。您仍然可以重用addTodo或派遣两个addTodo或使用中间件或发送compose(addTodo('One'), addTodo('Two'))。主要区别在于我们创建了对象和函数,并将其放置在状态发生变化的代码中。

答案 1 :(得分:4)

好问题。

将操作与状态更改分开实际上是一种Flux模式,而不是具体的Redux模式。 (虽然我将参考Redux回答这个问题。)这是松耦合的一个例子。

在一个简单的应用程序中,操作和状态更改之间的紧密耦合可能没问题。但在一个更大的应用程序中,这可能是一个令人头痛例如,您的addTodo操作可能会触发州内多个部分的更改。从状态更改中拆分操作 - 后者在reducers中执行 - 允许您编写更小的函数,这些函数更容易推理并且更易于测试。

此外,解耦操作和状态更改可使您的reducer逻辑更具可重用性。例如操作X可能触发状态更改A和B,而操作Y仅触发状态更改A.

此外,这种解耦会产生一个名为 middleware 的Redux功能。中间件监听操作调度。它不会改变应用程序的状态,但它可以读取当前状态和下一个状态,以及操作信息。中间件对于核心应用程序逻辑无关的功能非常有用,例如:记录和跟踪(在前面的答案中提到了开发工具)。

[更新]但为什么要将操作作为对象?

如果仅仅是调用其他函数的函数问题,那么解耦就不那么明确了。的确,它甚至可能会迷失;由于大多数操作只会实现一个状态更改,因此开发人员可能厌倦了单个函数调用单个函数并完全取消分离。

另一件事是Flux data flow model。单向数据流对于Flux / React范例非常重要。典型的Redux / React就是这样的:Store state -> Higher order React components -> Lower order React components -> DOM。动作是模型中的偏差;它是从视图向商店传输数据的向后箭头。让动作尽可能大声和强调是有道理的。不仅仅是函数调用,而是调度对象。就好像你的应用宣布嘿嘿!这里发生了重要的事情!

答案 2 :(得分:3)

动作和减速器之间没有一对一的映射。 Per Dan Abramov在https://github.com/Pitzcarraldo/reduxible/issues/8的评论:

  

它加强了对Redux的一个非常普遍的误解:即动作创建者和缩减者是一对一的映射。

     

这仅适用于简单的例子,但它在实际应用中极为有限。暴露于这种模式的初学者将减少者和行动创造者联系起来,并且没有意识到他们意味着多对多并且脱钩。

     

许多减速器可以处理一个动作。一个减速器可以处理许多动作。将它们放在一起否定了Flux和Redux应用程序如何扩展的许多好处。这导致代码膨胀和不必要的耦合。你失去了对来自不同地方的同一行动作出反应的灵活性,你的行动创造者开始像“设定者”一样,与特定的状态形状相结合,从而将组件耦合到它。

至于行动和"类型"参数,其他答案都是对的。这是故意设计Redux的原因,旨在为调试目的提供序列化的好处。