我的Redux状态的形状如下:
{
user: {
id: 123,
items: [1, 2]
},
items: {
1: {
...
},
2: {
...
}
}
}
使用combineReducers我有2套减速器。每个行为都在州的一个根键上。即,一个管理user
密钥,另一个管理items
密钥。
如果我想添加一个项目,我可以调用2个reducers,第一个将向items
添加一个新对象,第二个将id添加到user.items
数组。
这有很难的代码味道。我觉得应该有一种方法可以同时原子地减少两个对象的状态。即,除了子减速器之外还有一个根减速器作用于根对象。这可能吗?
答案 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
})
/**
* 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
添加到您的减速器开关!