Spec-Zone.ru › Redux 3

Использование логики редуктора

По мере роста приложения начнут проявляться общие шаблоны в логике редуктора. Вы можете обнаружить, что несколько частей логики редуктора выполняют одинаковую работу для различных типов данных и захотите уменьшить дублирование, повторно используя одинаковую общую логику для каждого типа данных. Или вы можете захотеть иметь несколько «экземпляров» определённого типа данных, обрабатываемых в хранилище. Однако глобальная структура хранилища Redux имеет свои компромиссы: она упрощает отслеживание общего состояния приложения, но также может затруднить «нацеливание» действий, которые должны обновить определённую часть состояния, особенно если вы используете combineReducers.

Например, предположим, что мы хотим отслеживать несколько счётчиков в приложении с именами A, B и C. Мы определяем наш начальный counter редуктор, и используем combineReducers для настройки нашего состояния:

function counter(state = 0, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

const rootReducer = combineReducers({
    counterA : counter,
    counterB : counter,
    counterC : counter
});

К сожалению, в этой настройке есть проблема. Поскольку combineReducers вызовет каждый редуктор раздела с тем же действием, отправка {type : 'INCREMENT'} фактически приведёт к инкременту всех трёх значений счётчиков, а не только одного из них. Нам нужен способ обернуть логику counter так, чтобы мы могли гарантировать обновление только нужного нам счётчика.

Настройка поведения с помощью редукторов высшего порядка

Как определено в Разделение логики редуктора, редуктор высшего порядка — это функция, которая принимает в качестве аргумента функцию-редуктор и/или возвращает новую функцию-редуктор в результате. Его также можно рассматривать как «фабрику редукторов». combineReducers — это один из примеров редуктора высшего порядка. Мы можем использовать этот шаблон для создания специализированных версий наших собственных функций-редукторов, при этом каждая версия отвечает только на определённые действия.

Два наиболее распространённых способа специализации редуктора — это создание новых констант действий с заданным префиксом или суффиксом или добавление дополнительной информации в объект действия. Вот как это может выглядеть:

function createCounterWithNamedType(counterName = '') {
    return function counter(state = 0, action) {
        switch (action.type) {
            case `INCREMENT_${counterName}`:
                return state + 1;
            case `DECREMENT_${counterName}`:
                return state - 1;
            default:
                return state;
        }
    }
}

function createCounterWithNameData(counterName = '') {
    return function counter(state = 0, action) {
        const {name} = action;
        if(name !== counterName) return state;

        switch (action.type) {
            case `INCREMENT`:
                return state + 1;
            case `DECREMENT`:
                return state - 1;
            default:
                return state;
        }
    }
}

Теперь мы должны иметь возможность использовать любой из этих методов для генерации наших специализированных редукторов счётчика и затем отправлять действия, которые будут влиять на ту часть состояния, которая нас интересует:

const rootReducer = combineReducers({
    counterA : createCounterWithNamedType('A'),
    counterB : createCounterWithNamedType('B'),
    counterC : createCounterWithNamedType('C'),
});

store.dispatch({type : 'INCREMENT_B'});
console.log(store.getState());
// {counterA : 0, counterB : 1, counterC : 0}

Мы также могли бы немного изменить подход и создать более универсальный редуктор высшего порядка, который принимает как заданную функцию-редуктор, так и имя или идентификатор:

function counter(state = 0, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

function createNamedWrapperReducer(reducerFunction, reducerName) {
    return (state, action) => {
        const {name} = action;
        const isInitializationCall = state === undefined;
        if(name !== reducerName && !isInitializationCall) return state;

        return reducerFunction(state, action);    
    }
}

const rootReducer = combineReducers({
    counterA : createNamedWrapperReducer(counter, 'A'),
    counterB : createNamedWrapperReducer(counter, 'B'),
    counterC : createNamedWrapperReducer(counter, 'C'),
});

Вы даже можете пойти дальше и создать универсальный редуктор высшего порядка для фильтрации:

function createFilteredReducer(reducerFunction, reducerPredicate) {
    return (state, action) => {
        const isInitializationCall = state === undefined;
        const shouldRunWrappedReducer = reducerPredicate(action) || isInitializationCall;
        return shouldRunWrappedReducer ? reducerFunction(state, action) : state;
    }
}

const rootReducer = combineReducers({
    // check for suffixed strings
    counterA : createFilteredReducer(counter, action => action.type.endsWith('_A')),
    // check for extra data in the action
    counterB : createFilteredReducer(counter, action => action.name === 'B'),
    // respond to all 'INCREMENT' actions, but never 'DECREMENT'
    counterC : createFilteredReducer(counter, action => action.type === 'INCREMENT')
};

Эти базовые шаблоны позволяют вам, например, иметь несколько экземпляров умного подключённого компонента в пользовательском интерфейсе или повторно использовать общую логику для таких общих возможностей, как постраничная навигация или сортировка.

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

Spec-Zone.ru

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