Redux 高阶reducer
参考网址:
https://zhuanlan.zhihu.com/p/21648398?refer=turborender https://segmentfault.com/a/1190000010205508
1 高阶reducer的定义
高阶reducer指的是一个函数,该函数接收一个reducer函数作为参数或者返回一个reducer函数作为函数的返回值。 高阶reducer也可以被看作一个reducer工厂,combineReducers是高阶reducer一个典型的例子。 可以使用高阶reducer函数来创建一个符合自己要求的reducer的函数。
2 高阶reducer的应用场景
2.1 应用场景一:不同的reducer具有相同的逻辑
当应用功能变大时,在reducer函数中那些通用的逻辑就会出现重复,容易发现很多reducer都是处理同样的逻辑,只是处理的数据不同而已。因此会想到重复使用reducer函数中那些通用的逻辑,从而减小应用的代码。
2.1.1 实例场景
有一个课程列表和一个排班列表。 reducers/classes.js:
export const classes = (state, { type, payload }) => {
switch (type) {
case 'classes/FETCH_SUCCESS':
return {
...state,
loading: false,
list: payload,
};
default:
return state;
}
}
};
reducers/jobs.js:
export const jobs = (state, { type, payload }) => {
switch (type) {
case ‘jobs/FETCH_SUCCESS':
return {
...state,
loading: false,
list: payload,
};
default:
return state;
}
}
}
2.1.2 高阶reducer的用法
两个reducer几乎是一样的,可以写一个reducer来把两个reducer相同的部分提取出来: reducers/list.js:
export const list = (actionType) => {
return (state=initialState, { type, payload }) => {
switch (type) {
case actionType:
return {
...state,
loading: false,
list: payload,
};
default:
return state;
}
};
};
reducers/classes.js:
const reducer = (state, action) => {
switch (action.type) {
// 其他逻辑
default:
return state;
}
};
export const classes = (state, action) => {
state = list(‘classes/FETCH_SUCCESS')(state, action);
return reducer(state, action);
};
reducers/jobs.js:
const reducer = (state, action) => {
switch (action.type) {
// 其他逻辑
default:
return state;
}
};
export const jobs = (state, action) => {
state = list('jobs/FETCH_SUCCESS')(state, action);
return reducer(state, action);
};
随着抽象的东西越来越多,reducer会变成这样: reducers/classes.js:
const reducer = (state, action) => {
switch (action.type) {
// 其他逻辑
default:
return state;
}
};
export const classes = (state, action) => {
state = list('classes/FETCH_SUCCESS')(state, action),
state = query('classes/FETCH_SUCCESS')(state, action),
state = pagination('classes/FETCH_SUCCESS')(state, action),
return reducer(state, action),
};
实现一个composeReducers组合这些reducer:
const composeReducers = (…reducers) => {
return (state = initialState, action) => {
if (reducers.length === 0) {
return state;
}
const last = reducers[reducers.length - 1];
const rest = reducers.slice(0, -1);
return rest.reduceRight((enhanced, reducer) => reducer(enhanced, action), last(state, action));
};
};
const reducer = (state, action) => {
switch (type) {
// 其他逻辑
default:
return state;
}
};
export const classes = composeReducers(
reducer,
list('classes/FETCH_SUCCESS'),
query('classes/FETCH_SUCCESS'),
pagination('classes/FETCH_SUCCESS’),
);
2.2 应用场景二:相同的reducer具有不同的逻辑
有时候想要在store中处理特定类型数据的多个“实例”,如counter函数需要用于多个地方。redux的全局store可以很容易跟踪整个应用的state状态(for循环,所有的reducer都执行了一遍),但是,很难针对特定的action来更新特定的state的某一部分数据,特别是当使用combineReducers(因为dispatch时,采用的是for循环来对每一个reducer都进行了执行)。
2.2.1 实例场景
想要跟踪应用中多个counter实例,分别为counterA、counterB、counterC。首先定义counter这个reducer,同时使用combineReducers来管理状态:
const 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,
});
问题:以上代码是错误的,每个reducer都会处理同样的action。原因是combineReducer采用的是for循环来对所有的reducer使用相同的action都执行一遍,因此如果dispacth({type:”INCREMENT”}),则counterA、counterB、counterC都会执行一遍,而不是执行某一个reducer函数。 解决:需要使用一种机制来保证只有一个reducer会执行,这种机制就是高阶reducer。
2.2.2 高阶reducer的用法
指定一个reducer最常用的方式就是使用一个后缀或前缀来产生一个reducer的action,或者将额外的信息添加到action对象上。
2.2.2.1 常规高阶reducer函数
可以使用任意一个函数来产生自己的reducer,然后dispatch一个action,而该action只会影响关心的那部分的state的值。
const createCounterWithName = (counterName = '') => {
return (state = 0, action) => {
switch (action.type) {
case `INCREMENT_${counterName}`:
return state + 1;
case `DECREMENT_${counterName}`:
return state - 1;
default:
return state;
}
};
};
const rootReducer = combineReducers({
counterA : createCounterWithName('A'),
counterB : createCounterWithName('B'),
counterC : createCounterWithName('C'),
});
2.2.2.2 高阶reducer的通用库multireducer
使用multireducer可以重用相同的reducer,解决dispatch一个action后,所有的reducer都执行一遍的情况。具体参见API。 multireducer可以让把同一个reducer挂载到不同状态树节点,而且自动处理对应的action。
import multireducer from 'multireducer';
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
const reducer = combineReducers({
posts: multireducer({
counterA: counter,
counterB: counter,
counterC: counter,
})
});
2.2.3 高阶reducer的好处
只用提供一个reducer函数,但却可以复用这部分的逻辑。只要dispatch的这个action明确指定需要改变哪一部分state状态即可。