Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →
За пределами combineReducers
Утилита combineReducers, встроенная в Redux, весьма полезна, но намеренно ограничена обработкой одного распространённого сценария: обновление дерева состояния в виде простого JavaScript-объекта путём делегирования работы по обновлению каждого среза состояния соответствующему срезовому редуктору. Она не обрабатывает другие случаи, такие как состояние в формате Immutable.js Maps, передача других частей дерева состояния в качестве дополнительного аргумента срезовому редуктору или упорядочивание вызовов срезовых редукторов. Её также не интересует, как конкретный срезовый редуктор выполняет свою работу.
Типичный вопрос: "Как использовать combineReducers для этих других сценариев?". Ответ прост: "никак — вероятно, вам нужно что-то другое". Когда вы выходите за рамки основного сценария combineReducers, пора применять более "кастомную" логику редукторов — будь то специфическая логика для разовой задачи или переиспользуемая функция для широкого применения. Ниже приведены рекомендации по работе с некоторыми типичными сценариями, но вы можете разработать свои подходы.
Обмен данными между срезовыми редукторами
Если sliceReducerA для обработки определённого действия нуждается в данных из среза sliceReducerB, или если sliceReducerB требует всё состояние целиком в качестве аргумента, combineReducers сам это не решает. Проблему можно устранить, написав кастомную функцию, передающую нужные данные как дополнительный аргумент в конкретных случаях:
function combinedReducer(state, action) {
switch (action.type) {
case 'A_TYPICAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
b: sliceReducerB(state.b, action)
}
}
case 'SOME_SPECIAL_ACTION': {
return {
// specifically pass state.b as an additional argument
a: sliceReducerA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
case 'ANOTHER_SPECIAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b: sliceReducerB(state.b, action, state)
}
}
default:
return state
}
}
Другой вариант решения проблемы "общих обновлений срезов" — просто поместить дополнительные данные в действие. Это легко реализуется через thunk-функции или подобные подходы:
function someSpecialActionCreator() {
return (dispatch, getState) => {
const state = getState()
const dataFromB = selectImportantDataFromB(state)
dispatch({
type: 'SOME_SPECIAL_ACTION',
payload: {
dataFromB
}
})
}
}
Поскольку данные из среза B уже содержатся в действии, родительскому редуктору не требуется специальная логика для предоставления этих данных sliceReducerA.
Третий подход: использовать редуктор, созданный combineReducers, для обработки "простых" случаев, когда каждый срезовый редуктор обновляется независимо, и дополнительный редуктор — для "особых" случаев, требующих обмена данными между срезами. Затем обёрточная функция может последовательно вызывать оба редуктора для формирования итогового результата:
const combinedReducer = combineReducers({
a: sliceReducerA,
b: sliceReducerB
})
function crossSliceReducer(state, action) {
switch (action.type) {
case 'SOME_SPECIAL_ACTION': {
return {
// specifically pass state.b as an additional argument
a: handleSpecialCaseForA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
default:
return state
}
}
function rootReducer(state, action) {
const intermediateState = combinedReducer(state, action)
const finalState = crossSliceReducer(intermediateState, action)
return finalState
}
Существует полезная утилита reduce-reducers, упрощающая этот процесс. Она принимает несколько редукторов и применяет к ним reduce(), передавая промежуточные значения состояния следующему редуктору в цепочке:
// Same as the "manual" rootReducer above
const rootReducer = reduceReducers(combinedReducers, crossSliceReducer)
Важно: при использовании reduceReducers убедитесь, что первый редуктор в списке может определить начальное состояние, так как последующие редукторы обычно предполагают, что всё состояние уже существует, и не пытаются задать значения по умолчанию.
Дополнительные рекомендации
Помните: редукторы в Redux — это обычные функции. Хотя combineReducers полезен, это всего лишь один инструмент в арсенале. Функции могут содержать условную логику помимо switch, композироваться для обёртки друг друга и вызывать другие функции. Например, если срезовому редуктору нужен сброс состояния и реакция только на определённые действия:
const undoableFilteredSliceA = compose(
undoReducer,
filterReducer('ACTION_1', 'ACTION_2'),
sliceReducerA
)
const rootReducer = combineReducers({
a: undoableFilteredSliceA,
b: normalSliceReducerB
})
Обратите внимание: combineReducers не знает и не интересуется особенностями редуктора, управляющего срезом a. Нам не потребовалось модифицировать combineReducers для реализации отмены — мы просто собрали нужные компоненты в новую композитную функцию.
Хотя combineReducers — единственная встроенная в Redux утилита для работы с редюсерами, существует множество сторонних инструментов для создания редюсеров, доступных для повторного использования. Каталог дополнений Redux содержит список многих доступных сторонних утилит. Если же ни одно из опубликованных решений не подходит для вашей задачи, вы всегда можете создать собственную функцию, которая будет делать именно то, что вам нужно.