Reusing Reducer Logic: Patterns for creating reusable reducers">Reusing Reducer Logic: Patterns for creating reusable reducers">
跳至主要内容

复用 Reducer 逻辑

随着应用程序的增长,reducer 逻辑中的常见模式将开始出现。您可能会发现 reducer 逻辑的几个部分对不同类型的数据执行相同类型的操作,并且希望通过对每种数据类型重用相同的通用逻辑来减少重复。或者,您可能希望在存储中处理某种类型数据的多个“实例”。但是,Redux 存储的全局结构有一些权衡:它使跟踪应用程序的整体状态变得容易,但也可能使“定位”需要更新特定状态片段的操作变得更加困难,尤其是在使用 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 会使用相同的操作调用每个切片 reducer,所以分派 {type : 'INCREMENT'} 实际上会导致所有三个计数器值都被递增,而不仅仅是其中一个。我们需要某种方法来包装 counter 逻辑,以便我们可以确保只更新我们关心的计数器。

使用高阶 reducer 自定义行为

拆分 Reducer 逻辑 中所定义,高阶 reducer 是一个函数,它以 reducer 函数作为参数,并/或返回一个新的 reducer 函数作为结果。它也可以被视为一个“reducer 工厂”。combineReducers 是高阶 reducer 的一个例子。我们可以使用这种模式来创建我们自己的 reducer 函数的专门版本,每个版本只响应特定的操作。

专门化 reducer 的两种最常见方法是使用给定的前缀或后缀生成新的操作常量,或者在操作对象中附加额外的信息。以下是它们可能的样子

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,然后分派将影响我们关心的状态部分的操作

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 creators,并且可以使用辅助函数同时生成它们。请参阅 Action/Reducer 生成器Reducers 库以获取 action/reducer 工具。

集合/项目 Reducer 模式

此模式允许你拥有多个状态,并使用一个通用 reducer 来根据 action 对象中的附加参数更新每个状态。

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;
}
}