【问题标题】:Why do you need 'Actions' as data in redux?为什么你需要'Actions'作为redux中的数据?
【发布时间】:2016-01-13 05:30:00
【问题描述】:

Redux documentations 说我应该制作动作和动作创建者,像这样:

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

然后写reducers,像这样:

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"));

看起来actions和reducers之间是一一对应的;该操作的唯一目的是选择一个减速器并为该减速器提供输入数据。

我们为什么不跳过中间人,用 reducer 来识别 action,用产生 reducer 的函数来识别 action creator?然后dispatch 将采用单个参数,即State => State 类型的reducer/action:

// 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。

那么,我缺少将操作作为数据的原因是什么?

【问题讨论】:

  • 阅读其他答案后,在我的答案中添加了一些注释。不知道它是否对 TypeScript 有帮助(可能你需要提供一些代码示例来显示确切的问题)。

标签: javascript typescript redux


【解决方案1】:

action 在 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') 也是声明性的)。

注意动作创建者和状态变化之间的耦合

只需比较两个符号:

第一:

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 或使用中间件或分派@ 987654331@。主要区别在于我们创建了Object和Function,并放置在状态变化的代码中。

【讨论】:

    【解决方案2】:

    actions 和 reducer 之间没有一对一的映射。 Per Dan Abramov 在https://github.com/Pitzcarraldo/reduxible/issues/8 的cmets:

    它强化了关于 Redux 的一个非常普遍的误解:即 action creators 和 reducers 是一对一的映射。

    这仅在琐碎的示例中是正确的,但在实际应用中却极为有限。接触过这种模式的初学者将 reducer 和 action creators 结合起来,却没有意识到它们是多对多和解耦的。

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

    关于actions和“type”参数,其他的答案都是对的。这就是 Redux 的设计方式,旨在为调试目的提供序列化的好处。

    【讨论】:

    • 他谈到了将 action creators 与 reducers 进行比较。动作创建者!=动作。动作是数组的一项(动作堆栈)。该数组将用于减少状态。
    【解决方案3】:

    好问题。

    将动作与状态更改分开实际上是一种 Flux 模式,而不是专门针对 Redux 的事情。 (虽然我会参考 Redux 来回答这个问题。)这是松散耦合的一个例子。

    在一个简单的应用程序中,动作和状态更改之间的紧密耦合可能没问题。但在更大的应用程序中,这可能会令人头疼。例如,您的 addTodo 操作可能会触发状态的多个部分的更改。从状态变化中分离动作——后者在 reducer 中执行——允许你编写更小的函数,这些函数更容易推理和测试。

    此外,将您的操作和状态更改解耦可以让您的 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。动作是模型中的像差;它是将数据从视图传输到存储的向后箭头。使动作尽可能响亮和强调是有意义的。不仅仅是一个函数调用,而是一个调度的对象。就好像您的应用正在宣布 嘿!这里发生了重要的事情!

    【讨论】:

    • 非常有帮助,谢谢。我看到了将动作和状态变化脱钩的理由。我不禁想知道你提到的 reducer 的组合性是否不能通过函数组合来实现——这个问题似乎与动作是否应该有明确的数据表示是正交的。
    • "不仅仅是一个函数调用,而是一个分派的对象。"但是函数也是对象......据我了解的问题 - 这是关于具有某种格式的文字对象的好处,而不是函数。请参阅我关于耦合的说明
    猜你喜欢
    • 1970-01-01
    • 2022-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-27
    • 2020-04-09
    • 1970-01-01
    相关资源
    最近更新 更多