以下内容直接来自 redux/normalizr 创建者的帖子:
所以你的状态看起来像:
{
entities: {
plans: {
1: {title: 'A', exercises: [1, 2, 3]},
2: {title: 'B', exercises: [5, 1, 2]}
},
exercises: {
1: {title: 'exe1'},
2: {title: 'exe2'},
3: {title: 'exe3'}
}
},
currentPlans: [1, 2]
}
你的减速器可能看起来像
import merge from 'lodash/object/merge';
const exercises = (state = {}, action) => {
switch (action.type) {
case 'CREATE_EXERCISE':
return {
...state,
[action.id]: {
...action.exercise
}
};
case 'UPDATE_EXERCISE':
return {
...state,
[action.id]: {
...state[action.id],
...action.exercise
}
};
default:
if (action.entities && action.entities.exercises) {
return merge({}, state, action.entities.exercises);
}
return state;
}
}
const plans = (state = {}, action) => {
switch (action.type) {
case 'CREATE_PLAN':
return {
...state,
[action.id]: {
...action.plan
}
};
case 'UPDATE_PLAN':
return {
...state,
[action.id]: {
...state[action.id],
...action.plan
}
};
default:
if (action.entities && action.entities.plans) {
return merge({}, state, action.entities.plans);
}
return state;
}
}
const entities = combineReducers({
plans,
exercises
});
const currentPlans = (state = [], action) {
switch (action.type) {
case 'CREATE_PLAN':
return [...state, action.id];
default:
return state;
}
}
const reducer = combineReducers({
entities,
currentPlans
});
那么这里发生了什么?首先,注意状态是标准化的。我们永远不会在其他实体中拥有实体。相反,它们通过 ID 相互引用。因此,每当某些对象发生变化时,只有一个地方需要更新。
其次,请注意我们如何通过在计划减速器中添加适当的实体并将其 ID 添加到 currentPlans 减速器来对 CREATE_PLAN 做出反应。这个很重要。在更复杂的应用程序中,您可能会有关系,例如,计划缩减程序可以通过将新 ID 附加到计划内的数组来以相同的方式处理 ADD_EXERCISE_TO_PLAN。但是如果练习本身更新了,则计划 reducer 不需要知道这一点,因为 ID 没有改变。
第三,注意实体reducers(计划和练习)有特殊的条款注意action.entities。这是为了以防我们有一个带有“已知事实”的服务器响应,我们想要更新我们所有的实体来反映。要在分派操作之前以这种方式准备数据,您可以使用 normalizr。你可以在 Redux repo 的“真实世界”示例中看到它的使用情况。
最后,注意实体化简器的相似之处。您可能想编写一个函数来生成这些。这超出了我的回答范围——有时你想要更多的灵活性,有时你想要更少的样板。您可以查看“真实世界”示例减速器中的分页代码,以获取生成类似减速器的示例。
哦,我使用了 { ...a, ...b } 语法。它在 Babel 阶段 2 中作为 ES7 提案启用。它被称为“对象扩展运算符”,相当于编写 Object.assign({}, a, b)。
至于库,您可以使用 Lodash(注意不要发生变异,例如 merge({}, a, b} 是正确的,但 merge(a, b) 不是)、updeep、react-addons-update 或其他。但是,如果您发现自己需要进行深度更新,则可能意味着您的状态树不够平坦,并且您没有充分利用功能组合。即使是您的第一个示例:
case 'UPDATE_PLAN':
return {
...state,
plans: [
...state.plans.slice(0, action.idx),
Object.assign({}, state.plans[action.idx], action.plan),
...state.plans.slice(action.idx + 1)
]
};
可以写成
const plan = (state = {}, action) => {
switch (action.type) {
case 'UPDATE_PLAN':
return Object.assign({}, state, action.plan);
default:
return state;
}
}
const plans = (state = [], action) => {
if (typeof action.idx === 'undefined') {
return state;
}
return [
...state.slice(0, action.idx),
plan(state[action.idx], action),
...state.slice(action.idx + 1)
];
};
// somewhere
case 'UPDATE_PLAN':
return {
...state,
plans: plans(state.plans, action)
};