Spec-Zone.ru › Redux 3

По ту сторону combineReducers

Утилита combineReducers, включенная в Redux, очень полезна, но она преднамеренно ограничена, чтобы обрабатывать один распространённый случай: обновление дерева состояния, которое представляет собой обычный JavaScript-объект, делегируя работу по обновлению каждого среза состояния конкретному редуктору среза. Она не обрабатывает другие случаи, такие как дерево состояния, составленное из Immutable.js Map, попытки передать другие части дерева состояния в качестве дополнительного аргумента редуктору среза или выполнить «упорядочение» вызовов редукторов срезов. Она также не заботится о том, как конкретный редуктор среза выполняет свою работу.

Тогда возникает общий вопрос: «Как я могу использовать combineReducers для обработки этих других случаев?». Ответ прост: «вы не должны — вам, вероятно, нужно использовать что-то другое». Как только вы выйдете за рамки основного использования combineReducers, пришло время использовать более «специализированную» логику редуктора, будь то специфическая логика для разового случая или многократно используемая функция, которую можно широко использовать. Ниже приведены некоторые рекомендации по работе с несколькими типичными случаями использования, но вы можете разработать свои собственные подходы.

Использование редукторов срезов с объектами Immutable.js

Поскольку combineReducers в настоящее время работает только с обычными JavaScript-объектами, приложение, использующее объект Immutable.js Map в верхней части своего дерева состояния, не сможет использовать combineReducers для управления этим Map. Поскольку многие разработчики используют Immutable.js, существует ряд опубликованных утилит, которые предоставляют эквивалентную функциональность, например, redux-immutable. Этот пакет предоставляет собственную реализацию combineReducers, которая умеет итерироваться по Immutable Map вместо обычного JavaScript-объекта.

Обмен данными между редукторами срезов

Аналогично, если 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 перечислены многие утилиты сторонних разработчиков. Или, если ни одна из опубликованных утилит не решает вашу задачу, вы всегда можете написать собственную функцию, которая сделает именно то, что вам нужно.

© 2015–2017 Dan Abramov
Licensed under the MIT License.
http://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API