Skip to main content

重用 Reducer 逻辑

¥Reusing Reducer Logic

随着应用的增长,reducer 逻辑中的常见模式将开始出现。你可能会发现 reducer 逻辑的多个部分对不同类型的数据执行相同类型的工作,并且希望通过为每种数据类型重用相同的通用逻辑来减少重复。或者,你可能希望在存储中处理某种类型数据的多个 "instances"。然而,Redux 存储的全局结构需要一些权衡:它使跟踪应用的整体状态变得容易,但也可能使需要更新特定状态的 "target" 操作变得更加困难,特别是当你使用 combineReducers 时。

¥As an application grows, common patterns in reducer logic will start to emerge. You may find several parts of your reducer logic doing the same kinds of work for different types of data, and want to reduce duplication by reusing the same common logic for each data type. Or, you may want to have multiple "instances" of a certain type of data being handled in the store. However, the global structure of a Redux store comes with some trade-offs: it makes it easy to track the overall state of an application, but can also make it harder to "target" actions that need to update a specific piece of state, particularly if you are using combineReducers.

举个例子,假设我们想要跟踪应用中的多个计数器,名为 A、B 和 C。我们定义初始的 counter reducer,并使用 combineReducers 来设置我们的状态:

¥As an example, let's say that we want to track multiple counters in our application, named A, B, and C. We define our initial counter reducer, and we use combineReducers to set up our state:

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 逻辑,以便确保只有我们关心的计数器被更新。

¥Unfortunately, this setup has a problem. Because combineReducers will call each slice reducer with the same action, dispatching {type : 'INCREMENT'} will actually cause all three counter values to be incremented, not just one of them. We need some way to wrap the counter logic so that we can ensure that only the counter we care about is updated.

使用高阶 reducer 定制行为

¥Customizing Behavior with Higher-Order Reducers

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

¥As defined in Splitting Reducer Logic, a higher-order reducer is a function that takes a reducer function as an argument, and/or returns a new reducer function as a result. It can also be viewed as a "reducer factory". combineReducers is one example of a higher-order reducer. We can use this pattern to create specialized versions of our own reducer functions, with each version only responding to specific actions.

专门化化简器的两种最常见的方法是生成具有给定前缀或后缀的新操作常量,或者在操作对象内附加附加信息。这些可能是这样的:

¥The two most common ways to specialize a reducer are to generate new action constants with a given prefix or suffix, or to attach additional info inside the action object. Here's what those might look like:

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

¥We should now be able to use either of these to generate our specialized counter reducers, and then dispatch actions that will affect the portion of the state that we care about:

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 函数和名称或标识符:

¥We could also vary the approach somewhat, and create a more generic higher-order reducer that accepts both a given reducer function and a name or identifier:

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:

¥You could even go as far as to make a generic filtering higher-order 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 中拥有智能连接组件的多个实例,或者重用通用逻辑来实现分页或排序等通用功能。

¥These basic patterns allow you to do things like having multiple instances of a smart connected component within the UI, or reuse common logic for generic capabilities such as pagination or sorting.

除了以这种方式生成 reducer 之外,你可能还想使用相同的方法生成动作创建器,并且可以使用辅助函数同时生成它们。有关操作/reducer 实用程序,请参阅 动作/reducer 生成器Reducer 库。

¥In addition to generating reducers this way, you might also want to generate action creators using the same approach, and could generate them both at the same time with helper functions. See Action/Reducer Generators and Reducers libraries for action/reducer utilities.

集合/物品缩减模式

¥Collection / Item Reducer Pattern

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

¥This pattern allows you to have multiple states and use a common reducer to update each state based on an additional parameter inside the action object.

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