更新 Redux 状态的多个部分的模式
Pattern for updating multiple parts of Redux state
我的 Redux 状态的形状如下所示:
{
user: {
id: 123,
items: [1, 2]
},
items: {
1: {
...
},
2: {
...
}
}
}
使用 combineReducers 我有 2 套减速器。每个都作用于状态的根键之一。即一个管理 user
键,另一个管理 items
键。
如果我想添加一个项目,我可以调用 2 个 reducer,第一个会向 items
添加一个新对象,第二个会将 id 添加到 user.items
数组。
这有不好的代码味道。我觉得应该有一种方法可以同时原子地减少两个对象的状态。即除了子减速器之外,还有一个作用于根对象的根减速器。这可能吗?
我觉得你做的其实是对的!
调度action时,从root-reducer开始,每"sub-reducer"都会调用一次,将对应的"sub-state"和action传递给sub-reducers的下一层。您可能认为这不是一个好的模式,因为每个 "sub-reducer" 都会被调用并一直向下传播到状态树的每个叶节点,但实际上并非如此!
如果动作定义在switch case中,"sub-reducer"只会改变它拥有的"sub-state"部分,并可能将动作传递给下一层,但如果动作不是' t 在 "sub-reducer" 中定义,它什么都不做,return 当前 "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()
用于状态树中的每一层。
所以你的减速器应该看起来像这样:
(为了说明层层概念,这里是outside-in,所以顺序倒过来了)
第一层:
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 将调用每个 sub-reducer 来自 rootReducer,
通过:
currentUser: {...}
sub-state 整个动作到 currentUserReducer
entities: {users: {...}, items: {...}}
和对 entitiesReducer
的操作
routing: {...}
和对 routeReducer
的操作
和...
entitiesReducer
会将 users: {...}
和操作传递给 usersReducer
,
items: {...}
和对 itemsReducer
的操作
为什么这么好?
所以你提到有没有办法让 root reducer 处理状态的不同部分,而不是将它们传递给单独的 sub-reducers。但是如果你不使用 reducer 组合并编写一个巨大的 reducer 来处理状态的每个部分,或者你只是将你的状态嵌套到一个深度嵌套的树中,那么随着你的应用程序变得越来越复杂(假设每个用户都有一个 [friends]
数组,或者项目可以有 [tags]
,等等),如果不是不可能找出每种情况,那将是非常复杂的。
此外,拆分 reducer 使您的应用程序非常灵活,您只需将任何 case TYPE_NAME
添加到 reducer 以对该操作做出反应(只要您的父 reducer 将其传递下去)。
例如,如果你想跟踪用户是否访问了某条路线,只需将 case UPDATE_PATH
添加到你的 reducer 开关中即可!
我的 Redux 状态的形状如下所示:
{
user: {
id: 123,
items: [1, 2]
},
items: {
1: {
...
},
2: {
...
}
}
}
使用 combineReducers 我有 2 套减速器。每个都作用于状态的根键之一。即一个管理 user
键,另一个管理 items
键。
如果我想添加一个项目,我可以调用 2 个 reducer,第一个会向 items
添加一个新对象,第二个会将 id 添加到 user.items
数组。
这有不好的代码味道。我觉得应该有一种方法可以同时原子地减少两个对象的状态。即除了子减速器之外,还有一个作用于根对象的根减速器。这可能吗?
我觉得你做的其实是对的!
调度action时,从root-reducer开始,每"sub-reducer"都会调用一次,将对应的"sub-state"和action传递给sub-reducers的下一层。您可能认为这不是一个好的模式,因为每个 "sub-reducer" 都会被调用并一直向下传播到状态树的每个叶节点,但实际上并非如此!
如果动作定义在switch case中,"sub-reducer"只会改变它拥有的"sub-state"部分,并可能将动作传递给下一层,但如果动作不是' t 在 "sub-reducer" 中定义,它什么都不做,return 当前 "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()
用于状态树中的每一层。
所以你的减速器应该看起来像这样: (为了说明层层概念,这里是outside-in,所以顺序倒过来了)
第一层:
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 将调用每个 sub-reducer 来自 rootReducer,
通过:
currentUser: {...}
sub-state 整个动作到 currentUserReducer
entities: {users: {...}, items: {...}}
和对 entitiesReducer
的操作
routing: {...}
和对 routeReducer
的操作
和...
entitiesReducer
会将 users: {...}
和操作传递给 usersReducer
,
items: {...}
和对 itemsReducer
为什么这么好?
所以你提到有没有办法让 root reducer 处理状态的不同部分,而不是将它们传递给单独的 sub-reducers。但是如果你不使用 reducer 组合并编写一个巨大的 reducer 来处理状态的每个部分,或者你只是将你的状态嵌套到一个深度嵌套的树中,那么随着你的应用程序变得越来越复杂(假设每个用户都有一个 [friends]
数组,或者项目可以有 [tags]
,等等),如果不是不可能找出每种情况,那将是非常复杂的。
此外,拆分 reducer 使您的应用程序非常灵活,您只需将任何 case TYPE_NAME
添加到 reducer 以对该操作做出反应(只要您的父 reducer 将其传递下去)。
例如,如果你想跟踪用户是否访问了某条路线,只需将 case UPDATE_PATH
添加到你的 reducer 开关中即可!