• Reducer 逻辑复用
    • 使用高阶 Reducer 来定制行为

    Reducer 逻辑复用

    随着应用程序的增长,在 reducer 逻辑中开始出现一些常见的模式。你可能会发现一部分 reducer 逻辑对于不同类型的数据做着相同的工作,你想通过对每种数据类型复用相同的公共逻辑来减少重复的代码。或者,你可能想要在 store 中处理某个类型的数据的多个”实例“。然而,Redux store 采用全局结构的设计本身就是一种折衷:优点是易于追踪应用程序的整体状态,但是,也可能更难的”命中“那些需要更新特定一部分状态的 action,特别是当你使用了 combineReducers

    例如,假设想在程序中追踪多个计数器,分别命名为A,B,和C。定义初始的 counter reducer,然后使用 combineReducers 去设置状态。

    1. function counter(state = 0, action) {
    2. switch (action.type) {
    3. case 'INCREMENT':
    4. return state + 1;
    5. case 'DECREMENT':
    6. return state - 1;
    7. default:
    8. return state;
    9. }
    10. }
    11. const rootReducer = combineReducers({
    12. counterA : counter,
    13. counterB : counter,
    14. counterC : counter
    15. });

    不幸的是,这样设置有一个问题。因为 combineReducers 将会使用相同的action调用每一个 reducer,发送 {type:'INCREMENT'} 实际上将会导致所有三个计数器的值被增加,而不仅仅是其中一个。我们需要一些方法去封装 counter 的逻辑,以此来保证只有我们关心的计数器被更新。

    使用高阶 Reducer 来定制行为

    正如在 Reducer 逻辑拆分 定义的那样,高阶 reducer 是一个接收 reducer 函数作为参数,并返回新的 reducer 函数的函数。它也可以被看作成一个“reducer 工厂”。combineReducers 就是一个高阶 reduce 的例子。我们可以使用这种模式来创建特定版本的 reducer 函数,每个版本只响应特定的 action。

    创建特定的 reducer 有两种最常见的方式,一个是使用给定的前缀或者后缀生成新的 action 常量,另一个是在 action 对象上附加额外的信息。下面是它们大概的样子:

    1. function createCounterWithNamedType(counterName = '') {
    2. return function counter(state = 0, action) {
    3. switch (action.type) {
    4. case `INCREMENT_${counterName}`:
    5. return state + 1;
    6. case `DECREMENT_${counterName}`:
    7. return state - 1;
    8. default:
    9. return state;
    10. }
    11. }
    12. }
    13. function createCounterWithNameData(counterName = '') {
    14. return function counter(state = 0, action) {
    15. const {name} = action;
    16. if(name !== counterName) return state;
    17. switch (action.type) {
    18. case `INCREMENT`:
    19. return state + 1;
    20. case `DECREMENT`:
    21. return state - 1;
    22. default:
    23. return state;
    24. }
    25. }
    26. }

    现在我们应该可以使用它们任何一个去生成我们特定的计数器 reducer,然后发送 action,并只会影响关心的那部分的 state:

    1. const rootReducer = combineReducers({
    2. counterA : createCounterWithNamedType('A'),
    3. counterB : createCounterWithNamedType('B'),
    4. counterC : createCounterWithNamedType('C'),
    5. });
    6. store.dispatch({type : 'INCREMENT_B'});
    7. console.log(store.getState());
    8. // {counterA : 0, counterB : 1, counterC : 0}

    我们在某种程度上也可以改变这个方法,创建一个更加通用的高阶 reducer,它可以接收一个给定的 reducer,一个名字或者标识符:

    1. function counter(state = 0, action) {
    2. switch (action.type) {
    3. case 'INCREMENT':
    4. return state + 1;
    5. case 'DECREMENT':
    6. return state - 1;
    7. default:
    8. return state;
    9. }
    10. }
    11. function createNamedWrapperReducer(reducerFunction, reducerName) {
    12. return (state, action) => {
    13. const {name} = action;
    14. const isInitializationCall = state === undefined;
    15. if(name !== reducerName && !isInitializationCall) return state;
    16. return reducerFunction(state, action);
    17. }
    18. }
    19. const rootReducer = combineReducers({
    20. counterA : createNamedWrapperReducer(counter, 'A'),
    21. counterB : createNamedWrapperReducer(counter, 'B'),
    22. counterC : createNamedWrapperReducer(counter, 'C'),
    23. });

    甚至还可以写一个通用的高阶 reducer 过滤器:

    1. function createFilteredReducer(reducerFunction, reducerPredicate) {
    2. return (state, action) => {
    3. const isInitializationCall = state === undefined;
    4. const shouldRunWrappedReducer = reducerPredicate(action) || isInitializationCall;
    5. return shouldRunWrappedReducer ? reducerFunction(state, action) : state;
    6. }
    7. }
    8. const rootReducer = combineReducers({
    9. // 检查后缀
    10. counterA : createFilteredReducer(counter, action => action.type.endsWith('_A')),
    11. // 检查 action 中的额外数据
    12. counterB : createFilteredReducer(counter, action => action.name === 'B'),
    13. // 响应所有的 'INCREMENT' action,但不响应 'DECREMENT'
    14. counterC : createFilteredReducer(counter, action => action.type === 'INCREMENT')
    15. });

    这些基本的模式可以让你在 UI 内处理一个智能连接的 component 的多个实例。对于像分页或者排序这些通用的功能,可以复用相同的逻辑。