我的Redux状态的形状如下所示:
{ user: { id: 123, items: [1, 2] }, items: { 1: { ... }, 2: { ... } } }
使用combineReducers我有2套减速器.每个行为都在州的一个根键上.即一个管理user
密钥,另一个管理items
密钥.
如果我想添加一个项目,我可以调用2个reducers,第一个将添加一个新对象,items
第二个将id添加到user.items
数组.
这有很难的代码味道.我觉得应该有一种方法可以同时原子地减少两个对象的状态.即除了子减速器之外还有一个根减速器作用于根对象.这可能吗?
我认为你所做的事实上是正确的!
当从root-reducer开始调度动作时,将调用每个"sub-reducer",将相应的"子状态"和动作传递给下一层sub-redurs.您可能认为这不是一个好的模式,因为每个"sub-reducer"都会被调用并一直传播到状态树的每个叶节点,但事实并非如此!
如果在切换情况下定义了操作,则"子减速器"将仅更改其拥有的"子状态"部分,并且可能将操作传递到下一层,但是如果操作未在" sub-reducer",它将什么都不做并返回当前的"子状态",这会停止传播.
让我们看一个更复杂的状态树的例子!
假设您使用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()
在状态树的每一层.
所以你的reducer应该是这样的:(为了说明逐层概念,这是从外到内的,所以顺序是向后的)
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
})
/**
* 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:
}
}
终极版将调用每个子减速从rootReducer,
路过:
currentUser: {...}
子状态和整个行动currentUserReducer
entities: {users: {...}, items: {...}}
和行动entitiesReducer
routing: {...}
和行动routeReducer
和......
entitiesReducer
将通过users: {...}
和行动usersReducer
,
并items: {...}
和行动itemsReducer
所以你提到有一种方法可以让根减速器处理状态的不同部分,而不是将它们传递给单独的子减速器.但是如果你不使用reducer组合并编写一个巨大的reducer来处理状态的每个部分,或者你只是将状态嵌套到一个深度嵌套的树中,那么当你的app变得更复杂时(比如每个用户都有一个[friends]
数组,或者物品可以有[tags]
,等等,如果不是不可能弄清楚每一个案例,那将是非常复杂的.
此外,拆分缩减器使您的应用程序非常灵活,您只需添加任何case TYPE_NAME
减速器以响应该操作(只要您的父减速器将其传递下去).
例如,如果您想跟踪用户是否访问某个路线,只需将其添加case UPDATE_PATH
到您的减速器开关即可!