Ответ 1
Ниже приведено сообщение от создателя 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
});
Итак, что здесь происходит? Во-первых, обратите внимание, что состояние нормировано. У нас никогда не было объектов внутри других объектов. Вместо этого они ссылаются друг на друга по идентификаторам. Поэтому, когда какой-либо объект изменяется, есть только одно место, где его нужно обновлять.
Во-вторых, обратите внимание на то, как мы реагируем на CREATE_PLAN, добавляя соответствующий объект в редуктор планов и добавляя его идентификатор к редуктору currentPlans. Это важно. В более сложных приложениях у вас могут быть отношения, например. Плановый редуктор может обрабатывать ADD_EXERCISE_TO_PLAN таким же образом, добавив новый идентификатор в массив внутри плана. Но если само обновление обновлено, нет необходимости в сокращении планов, чтобы знать, что, поскольку идентификатор не изменился.
В-третьих, обратите внимание, что редукторы объектов (планы и упражнения) имеют специальные предложения, отслеживающие действия. Это в случае, если у нас есть ответ сервера с "известной правдой", который мы хотим обновить всеми нашими сущностями, чтобы отразить. Чтобы подготовить данные таким образом перед отправкой действия, вы можете использовать normalizr. Вы можете видеть, что он используется в примере "реального мира" в репозитории Redux.
Наконец, обратите внимание на то, как редукторы объектов схожи. Возможно, вы захотите написать функцию для их создания. Это из-за моего ответа - иногда вам нужна большая гибкость, а иногда вы хотите меньше шаблонов. Вы можете проверить код разбивки на страницы в примерах редукторов реального мира для примера создания подобных редукторов.
О, и я использовал синтаксис {... a,... b}. Он был включен в Babel stage 2 в качестве предложения ES7. Он называется "оператор распространения объектов" и эквивалентен записи Object.assign({}, a, b).
Что касается библиотек, вы можете использовать Lodash (будьте осторожны, чтобы не мутировать, например, merge ({}, a, b} корректно, но слияние (a, b) отсутствует), updeep, update-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)
};