本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
复用 Reducer 逻辑
随着应用规模增长,reducer 逻辑中会逐渐出现通用模式。您可能发现多个 reducer 在处理不同数据类型时执行着相似操作,此时可以通过复用相同逻辑来减少重复代码。或者,您可能需要在 store 中处理多个同类型数据的"实例"。然而,Redux store 的全局结构存在一定权衡:它便于追踪应用整体状态,但也使得针对特定状态片段触发更新更加困难,尤其是在使用 combineReducers 时。
例如,假设我们需要在应用中跟踪名为 A、B 和 C 的多个计数器。首先定义初始的 counter reducer,然后使用 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 会将相同 action 传递给每个切片 reducer,当派发 {type : 'INCREMENT'} 时,实际上会导致_所有三个_计数器值增加,而非仅更新目标计数器。我们需要封装 counter 逻辑,确保仅更新特定计数器。
使用高阶 Reducer 定制行为
根据拆分 Reducer 逻辑的定义,高阶 reducer 是指接收 reducer 函数作为参数并返回新 reducer 的函数,可视为"reducer 工厂"。combineReducers 就是高阶 reducer 的典型示例。我们可以运用此模式创建定制化的 reducer 版本,使每个版本仅响应特定 action。
定制 reducer 的两种常见方式是:为 action 常量添加特定前缀/后缀,或在 action 对象中附加额外信息。示例如下:
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
}
}
}
现在我们可以使用这些方法生成定制计数器 reducer,并派发仅影响目标状态的 action:
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 incrementCounter(type = 'A') {
return {
type: `INCREMENT_${type}`
}
}
store.dispatch(incrementCounter('C'))
console.log(store.getState())
// {counterA : 0, counterB : 1, counterC : 1}
我们还可以稍作变通,创建更通用的高阶 reducer,使其同时接收 reducer 函数和名称标识符:
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')
})
更进一步,您可以实现通用的过滤型高阶 reducer:
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')
};
这些基础模式支持实现诸如在 UI 中部署多个智能连接组件,或复用分页/排序等通用功能逻辑。
除生成 reducer 外,您也可以用相同方式生成 action 创建器,并通过辅助函数同时生成两者。请参阅 Action/Reducer 生成器和Reducers库获取相关工具。
集合/项 Reducer 模式
此模式允许管理多个状态,并通过 action 对象中的附加参数使用通用 reducer 更新每个状态。
function counterReducer(state, action) {
switch(action.type) {
case "INCREMENT" : return state + 1;
case "DECREMENT" : return state - 1;
}
}
function countersArrayReducer(state, action) {
switch(action.type) {
case "INCREMENT":
case "DECREMENT":
return state.map( (counter, index) => {
if(index !== action.index) return counter;
return counterReducer(counter, action);
});
default:
return state;
}
}
function countersMapReducer(state, action) {
switch(action.type) {
case "INCREMENT":
case "DECREMENT":
return {
...state,
state[action.name] : counterReducer(state[action.name], action)
};
default:
return state;
}
}