Reducer Composition - 为什么我们需要用 Object.assign() 重新分配待办事项,为什么不直接调用另一个 reducer?

Reducer Composition - why do we need to re assign todos with Object.assign() why not just call the other reducer?

在这个 reducer 组合示例中,为什么我们不能 return 我们正在调用的 reducer?意思是,在 todoApp 减速器中,在 ADD_TODOTOGGLE_TODO 的情况下,我们使用另一个减速器:todos。我明白那个。但我不明白为什么我们不能将 todoApp 中的大小写设置为

function todoApp(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
    case TOGGLE_TODO:
      return todos(state.todos, action)

与我们这里的对比:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
    case TOGGLE_TODO:
       return Object.assign({}, state, {
         todos: todos(state.todos, action)
       })

特别是因为 todos reducer 正在 returning 完整的新状态对象,对吧?我们不是仍然遵循 redux 规则而不是通过 returning 第二个 reducer 的结果来修改状态吗?

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

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

redux 的主要概念是你永远不能直接修改状态,你必须 return 一个新的状态。您提出的解决方案是修改当前状态,这是不允许的。

Especially since the todos reducer is returning the full new state object, right?1

他们这里的关键词是'new'。它必须是一个新的状态对象,而不是对当前状态对象的修改。

你问的是为什么

case TOGGLE_TODO:
  return Object.assign({}, state, {
    todos: todos(state.todos, action)
  })

...不能只是这样...

case TOGGLE_TODO:
  return todos(state.todos, action)

你必须注意你正在使用的减速器的特定上下文中的状态形状

  • todo 的状态形状为 [Todo] – 其中 [Todo] 是 todos[=45 的 Array =]

  • todoApp 的状态形状为 {todos: [Todo]}

如果您要使用建议的代码

case TOGGLE_TODO:
  return todos(state.todos, action)

知道 todos 将 return [Todo],您将替换您的 todoApp 状态 ...

{todos: [Todo]}

...与

[Todo]

...这不是正确的形状。


So if the shape were the same this would be ok, I wouldn't be modifying the state otherwise, correct? – Anna Garcia 25 mins ago

正确,但他们在这种情况下使用 Object.assign 的原因是具有前瞻性的最佳实践。

你可以做到

case TOGGLE_TODO:
  return {todos: todos(state.todos, action)}

但是,有理由认为您的应用程序需要关注其他 "slices" 状态 – 而不仅仅是 todos。在这种情况下,TOGGLE_TODO 操作仅作用于 todos 切片。请注意以不会破坏此上下文中所有其他现有属性的方式更新 todos

假设我们的应用程序有另外两个状态片段,foobar。如果我们这样做...

// where state = {foo: 1, bar: 2, todos: [ ... ]}
case TOGGLE_TODO:
  return {todos: todos(state.todos, action)}

那么我们只 returning {todos: ...}foobar properties/values 已被删除!不好!

相反,我们必须小心保留其他状态条目,并且对我们打算修改的状态进行操作

// where state = {foo: 1, bar: 2, todos: [ ... ]}
case TOGGLE_TODO:
  return Object.assign({}, state, {todos: todos(state.todos, action)})

现在我们回去

{foo: 1, bar: 2, todos: newTodosArray}

这正是我们想要的——所有 foobartodos 属性仍处于 todoApp 状态。


对象传播语法

有人提议支持对象的扩展语法,这可以将 Object.assign 示例简化为

case TOGGLE_TODO:
  return {...state, todos: todos(state.todos, action)}

但在它进入 ECMAScript 之前,您必须使用 transform-object-rest-spread

对其进行 babelify