从后通过了Redux/normalizr创作者here以下是直接:
所以,你的状态看起来像:
{
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 reducer来对CREATE_PLAN做出反应。这个很重要。在更复杂的应用程序中,您可能有关系,例如计划缩减器可以通过向计划内的数组附加新ID来以相同的方式处理ADD_EXERCISE_TO_PLAN。但是如果练习本身已经更新,那么就不需要计划减速器知道这一点,因为ID没有改变。
第三,请注意,实体reducer(计划和练习)有特殊的条款注意action.entities。这是因为我们有一个服务器响应与“已知的真相”,我们想要更新我们所有的实体来反映。要在分派操作之前以这种方式准备数据,可以使用normalizr。您可以在Redux回购中的“现实世界”示例中看到它。
最后,注意实体reducer是如何相似的。你可能想编写一个函数来生成这些函数。它超出了我的答案的范围 - 有时你想要更多的灵活性,有时候你想要更少的样板。您可以查看“真实世界”示例缩减程序中的分页代码,以获取生成类似缩减程序的示例。
哦,我用{... a,... b}语法。它作为ES7提案在Babel阶段2中启用。它被称为“对象扩展运算符”,相当于写入Object.assign({},a,b)。对于库,你可以使用Lodash(注意不要改变,例如merge({},a,b}是正确的,但合并(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)
]
};
CAN写为
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)
};
谢谢@ AR7这个精彩的解释。我有一个问题:为什么我们需要将currentPlans数组保持在状态并保持它的更新(当然,如果你拥有状态,当然最少更新它,但是它在别处使用了什么) ?在这个州制定计划的目标还不够吗?它在实践中用于什么?我注意到Redux文档以及normalizr文档提到了这些数组。 – Cedric
@Cedric从我的角度来看,它用于保持对象的顺序。哈希映射没有顺序,所以如果只保留计划对象,每次刷新页面顺序都可能完全不同。你也不能遍历任何MVC框架中的对象,所以你需要在响应中做一些像Object.keys(plans).map()这样的事情,而不是仅仅使用当前的计划数组。 – m0meni
@Cedric也没有写这个哈哈。它本身就是redux/normalizr的创造者! – m0meni