Редукторы Redux - изменение глубоко вложенного состояния
Я пытаюсь обвести голову, как изменить глубоко вложенное состояние в redux. Мне разумно комбинировать редукторы и менять свойства первого уровня состояния для этих предметов. Там, где я не совсем ясно, как изменить состояние глубоко вложенного имущества.
Предположим, что у меня есть приложение для покупок. Ниже мое состояние:
{
cart: {
items: []
},
account: {
amountLeft: 100,
discounts: {
redeemed: [],
coupons: {
buyOneGetOne: false
}
}
}
}
Когда пользователь вводит код, скажем, он может выкупить купон "buyOneGetOne", и это значение должно стать истинным.
У меня есть редуктор для тележки, а другой для счета. Для свойства первого уровня (например, если я очищал элементы корзины), я бы просто сделал следующее в моем редукторе:
case 'EMPTY_CART':
return Object.assign({}, state, {items: []});
Однако для изменения buyOneGetOne мне кажется, что мне сначала нужно сделать Object.assign на купонах (потому что buyOneGetOne был изменен), а затем сделать Object.assign на скидках (потому что я изменил купоны), а затем, наконец, испуская действие, поэтому редуктор мог бы выполнить Object.assign на счету (поскольку скидка теперь изменилась). Это кажется действительно сложным и легким для того, чтобы сделать что-то неправильное, но это заставляет меня думать, что должен быть лучший способ.
Неужели я все это делаю неправильно? Кажется, что редукторы используются только для изменения свойств корневого уровня состояния (например, для корзины и учетной записи) и что у меня не должно быть редуктора, который затрагивает состояние внутри учетной записи (например, редуктор скидок), потому что учетная запись уже имеет редуктор, Но когда я хочу изменить одно свойство далеко вниз по дереву состояний, он становится сложным, чтобы объединить каждый объект из этого изменения вплоть до цепочки объектов к дочернему элементу корня...
Можете ли вы/должны иметь редукторы внутри редукторов, например, в этом случае есть скидки?
Ответы
Ответ 1
У вас могут быть редукторы внутри редукторов; на самом деле, demo-приложение redux делает это: http://redux.js.org/docs/basics/Reducers.html.
Например, вы можете сделать вложенный редуктор под названием "скидки":
function discounts(state, action) {
switch (action.type) {
case ADD_DISCOUNT:
return state.concat([action.payload]);
// etc etc
}
}
И затем используйте этот редуктор в редукторе вашего аккаунта:
function account(state, action) {
switch (action.type) {
case ADD_DISCOUNT:
return {
...state,
discounts: discounts(state.discounts, action)
};
// etc etc
}
}
Чтобы узнать больше, посмотрите egghead.io redux series самим Дэном, в частности редукционная композиция видео!
Ответ 2
вам нужно вложить combinedReducers
для каждого уровня глубиной
Ответ 3
Да, разделение этих редукторов - это правильная вещь. На самом деле, если вы боитесь, что некоторые из этих действий потребуют изменения другого редуктора, вы можете либо отреагировать на другую константу, либо просто отправить другое действие.
Я создал библиотеку для решения этой проблемы - чтобы упростить составление и избежать подробного синтаксиса, а также слияние результата,
Итак, ваш пример будет выглядеть следующим образом:
import { createTile, createSyncTile } from 'redux-tiles';
const cartItems = createSyncTile({
type: ['account', 'cart'],
fn: ({ params }) => ({ items: params.items })
});
const coupons = createSyncTile({
type: ['account', 'coupons'],
fn: ({ params }) => params,
});
const reedemedCoupons = createSyncTile({
type: ['account', 'reedemedCoupons'],
fn: ({ params }) => params.coupons
});
const account = createTile({
type: ['account', 'info'],
fn: ({ api }) => api.get('/client/info')
});
Используя эту стратегию, каждый компонент является атомарным, и вы можете легко создавать новые и отправлять другие, не затрагивая других, это упрощает реорганизацию и удаление некоторых функций в будущем, когда ваши "плитки" следуют принципу единой ответственности.