用于更新Redux状态的多个部分的模式

时间:2015-12-30 14:38:41

标签: javascript redux

我的Redux状态的形状如下:

{
  user: {
    id: 123,
    items: [1, 2]
  },
  items: {
    1: {
      ...
    },
    2: {
      ...
    }
  }
}

使用combineReducers我有2套减速器。每个行为都在州的一个根键上。即,一个管理user密钥,另一个管理items密钥。

如果我想添加一个项目,我可以调用2个reducers,第一个将向items添加一个新对象,第二个将id添加到user.items数组。

这有很难的代码味道。我觉得应该有一种方法可以同时原子地减少两个对象的状态。即,除了子减速器之外还有一个根减速器作用于根对象。这可能吗?

1 个答案:

答案 0 :(得分:24)

我认为你所做的事实上是正确的!

当调度动作时,从root-reducer开始,每个" sub-reducer"将被调用,通过相应的"子状态"并对下一层次减速器采取行动。您可能会认为这不是一个好的模式,因为每个" sub-reducer"被调用并一直传播到状态树的每个叶子节点,但事实并非如此!

如果在开关盒中定义了动作,那么"子减速器"只会改变"子状态"它拥有的部分,并且可能将动作传递给下一层,但如果动作未在" sub-reducer"中定义,它将不执行任何操作并返回当前的" sub -state",停止传播。

让我们看一个更复杂的状态树的例子!

假设您使用redux-simple-router,并且我将您的案例扩展为更复杂(拥有多个用户的数据),那么您的州树可能看起来像这样:

{
  currentUser: {
    loggedIn: true,
    id: 123,
  },
  entities: {
    users: {
      123: {
        id: 123,
        items: [1, 2]
      },
      456: {
        id: 456,
        items: [...]
      }
    },
    items: {
      1: {
        ...
      },
      2: {
        ...
      }
    }
  },
  routing: {
    changeId: 3,
    path: "/",
    state: undefined,
    replace:false
  }
}

正如您所看到的,状态树中有 嵌套 图层,为了解决这个问题,我们使用reducer composition,其概念是对状态树中的每个图层使用 combineReducer()

所以你的减速器应该是这样的: (为了说明逐层概念,这是从外到内的,因此顺序是向后的)

第一层:

import { routeReducer } from 'redux-simple-router'

function currentUserReducer(state = {}, action) {
  switch (action.type) {...}
}

const rootReducer = combineReducers({
  currentUser: currentUserReducer,
  entities: entitiesReducer, // from the second layer
  routing: routeReducer      // from 'redux-simple-router'
})

第二层(实体部分):

function usersReducer(state = {}, action) {
  switch (action.type) {
  case ADD_ITEM:
  case TYPE_TWO:
  case TYPE_TREE:
    return Object.assign({}, state, {
      // you can think of this as passing it to the "third layer"
      [action.userId]: itemsInUserReducer(state[action.userId], action)
    })
  case TYPE_FOUR:
    return ...
  default:
    return state
  }
}

function itemsReducer(...) {...}

const entitiesReducer = combineReducers({
  users: usersReducer,
  items: itemsReducer
})

第三层(entities.users.items):

/**
 * Note: only ADD_ITEM, TYPE_TWO, TYPE_TREE will be called here,
 *       no other types will propagate to this reducer
 */
function itemsInUserReducer(state = {}, action) {
  switch (action.type) {
  case ADD_ITEM:
    return Object.assign({}, state, {
      items: state.items.concat([action.itemId])
      // or items: [...state.items, action.itemId]
    })
  case TYPE_TWO:
    return DO_SOMETHING
  case TYPE_TREE:
    return DO_SOMETHING_ELSE
  default:
    state:
  }
}

当某个动作发送

redux将从rootReducer调用每个子减速器,
传球:
currentUser: {...}子状态和currentUserReducer的整个行动 entities: {users: {...}, items: {...}}和对entitiesReducer的行动 routing: {...}和对routeReducer的行动 和...
entitiesReducer会将users: {...}和操作传递给usersReducer
items: {...}以及对itemsReducer的行动

为什么这样好?

所以你提到有一种方法可以让root reducer处理状态的不同部分,而不是将它们传递给单独的sub-redurs。但是如果你不使用reducer组合并编写一个巨大的reducer来处理状态的每个部分,或者你只是将状态嵌套到一个深度嵌套的树中,那么当你的app变得更复杂时(比如每个用户都有一个{ {1}}数组,或者项目可以有[friends]等),如果不是不可能弄清楚每个案例,那将是非常复杂的。

此外,拆分缩减器使您的应用程序非常灵活,您只需将任何[tags]添加到减速器以对该操作做出反应(只要您的父减速器将其传递下去)。

例如,如果您想要跟踪用户是否访问某个路线,只需将case TYPE_NAME添加到您的减速器开关!