跳至主内容
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

使用功能分解和 Reducer 组合重构 Reducer 逻辑

了解不同类型的子 reducer 函数示例及其组合方式会很有帮助。下面我们将演示如何将大型单体 reducer 函数重构为多个小型函数的组合。

注意:本例有意采用详细写法,旨在阐明重构概念和过程,而非追求代码绝对简洁。

初始 Reducer

假设我们的初始 reducer 如下所示:

const initialState = {
visibilityFilter: 'SHOW_ALL',
todos: []
}

function appReducer(state = initialState, action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER': {
return Object.assign({}, state, {
visibilityFilter: action.filter
})
}
case 'ADD_TODO': {
return Object.assign({}, state, {
todos: state.todos.concat({
id: action.id,
text: action.text,
completed: false
})
})
}
case 'TOGGLE_TODO': {
return Object.assign({}, state, {
todos: state.todos.map(todo => {
if (todo.id !== action.id) {
return todo
}

return Object.assign({}, todo, {
completed: !todo.completed
})
})
})
}
case 'EDIT_TODO': {
return Object.assign({}, state, {
todos: state.todos.map(todo => {
if (todo.id !== action.id) {
return todo
}

return Object.assign({}, todo, {
text: action.text
})
})
})
}
default:
return state
}
}

该函数虽然较短,但已显得过于复杂。我们同时处理两个不同关注点(过滤 vs 管理待办事项列表),嵌套结构使更新逻辑更难阅读,且某些部分不够清晰。

提取工具函数

良好的第一步是抽离工具函数来返回带更新字段的新对象。同时可将更新数组中特定项目的重复模式提取为函数:

function updateObject(oldObject, newValues) {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues)
}

function updateItemInArray(array, itemId, updateItemCallback) {
const updatedItems = array.map(item => {
if (item.id !== itemId) {
// Since we only want to update one item, preserve all others as they are now
return item
}

// Use the provided callback to create an updated item
const updatedItem = updateItemCallback(item)
return updatedItem
})

return updatedItems
}

function appReducer(state = initialState, action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER': {
return updateObject(state, { visibilityFilter: action.filter })
}
case 'ADD_TODO': {
const newTodos = state.todos.concat({
id: action.id,
text: action.text,
completed: false
})

return updateObject(state, { todos: newTodos })
}
case 'TOGGLE_TODO': {
const newTodos = updateItemInArray(state.todos, action.id, todo => {
return updateObject(todo, { completed: !todo.completed })
})

return updateObject(state, { todos: newTodos })
}
case 'EDIT_TODO': {
const newTodos = updateItemInArray(state.todos, action.id, todo => {
return updateObject(todo, { text: action.text })
})

return updateObject(state, { todos: newTodos })
}
default:
return state
}
}

这减少了重复代码并提升了可读性。

提取 Case Reducer

接下来,我们将每个具体 case 拆分为独立函数:

// Omitted
function updateObject(oldObject, newValues) {}
function updateItemInArray(array, itemId, updateItemCallback) {}

function setVisibilityFilter(state, action) {
return updateObject(state, { visibilityFilter: action.filter })
}

function addTodo(state, action) {
const newTodos = state.todos.concat({
id: action.id,
text: action.text,
completed: false
})

return updateObject(state, { todos: newTodos })
}

function toggleTodo(state, action) {
const newTodos = updateItemInArray(state.todos, action.id, todo => {
return updateObject(todo, { completed: !todo.completed })
})

return updateObject(state, { todos: newTodos })
}

function editTodo(state, action) {
const newTodos = updateItemInArray(state.todos, action.id, todo => {
return updateObject(todo, { text: action.text })
})

return updateObject(state, { todos: newTodos })
}

function appReducer(state = initialState, action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return setVisibilityFilter(state, action)
case 'ADD_TODO':
return addTodo(state, action)
case 'TOGGLE_TODO':
return toggleTodo(state, action)
case 'EDIT_TODO':
return editTodo(state, action)
default:
return state
}
}

现在每个 case 的处理逻辑变得_非常_清晰,同时也能看出某些模式正在显现。

按领域分离数据处理

我们的 app reducer 仍知晓应用的所有 case。尝试将筛选逻辑与待办事项逻辑分离:

// Omitted
function updateObject(oldObject, newValues) {}
function updateItemInArray(array, itemId, updateItemCallback) {}

function setVisibilityFilter(visibilityState, action) {
// Technically, we don't even care about the previous state
return action.filter
}

function visibilityReducer(visibilityState = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return setVisibilityFilter(visibilityState, action)
default:
return visibilityState
}
}

function addTodo(todosState, action) {
const newTodos = todosState.concat({
id: action.id,
text: action.text,
completed: false
})

return newTodos
}

function toggleTodo(todosState, action) {
const newTodos = updateItemInArray(todosState, action.id, todo => {
return updateObject(todo, { completed: !todo.completed })
})

return newTodos
}

function editTodo(todosState, action) {
const newTodos = updateItemInArray(todosState, action.id, todo => {
return updateObject(todo, { text: action.text })
})

return newTodos
}

function todosReducer(todosState = [], action) {
switch (action.type) {
case 'ADD_TODO':
return addTodo(todosState, action)
case 'TOGGLE_TODO':
return toggleTodo(todosState, action)
case 'EDIT_TODO':
return editTodo(todosState, action)
default:
return todosState
}
}

function appReducer(state = initialState, action) {
return {
todos: todosReducer(state.todos, action),
visibilityFilter: visibilityReducer(state.visibilityFilter, action)
}
}

注意:由于两个"状态切片" reducer 现在只接收整体状态中自己负责的部分作为参数,它们不再需要返回复杂的嵌套状态对象,因此变得更简洁。

减少样板代码

我们即将完成。由于很多人不喜欢 switch 语句,常见的做法是使用创建 action 类型到 case 函数查找表的函数。我们将使用减少样板代码中描述的 createReducer 函数:

// Omitted
function updateObject(oldObject, newValues) {}
function updateItemInArray(array, itemId, updateItemCallback) {}

function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}

// Omitted
function setVisibilityFilter(visibilityState, action) {}

const visibilityReducer = createReducer('SHOW_ALL', {
SET_VISIBILITY_FILTER: setVisibilityFilter
})

// Omitted
function addTodo(todosState, action) {}
function toggleTodo(todosState, action) {}
function editTodo(todosState, action) {}

const todosReducer = createReducer([], {
ADD_TODO: addTodo,
TOGGLE_TODO: toggleTodo,
EDIT_TODO: editTodo
})

function appReducer(state = initialState, action) {
return {
todos: todosReducer(state.todos, action),
visibilityFilter: visibilityReducer(state.visibilityFilter, action)
}
}

按切片组合 Reducers

最后一步,使用 Redux 内置的 combineReducers 工具处理顶层 app reducer 的"状态切片"逻辑。最终结果如下:

// Reusable utility functions

function updateObject(oldObject, newValues) {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues)
}

function updateItemInArray(array, itemId, updateItemCallback) {
const updatedItems = array.map(item => {
if (item.id !== itemId) {
// Since we only want to update one item, preserve all others as they are now
return item
}

// Use the provided callback to create an updated item
const updatedItem = updateItemCallback(item)
return updatedItem
})

return updatedItems
}

function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}

// Handler for a specific case ("case reducer")
function setVisibilityFilter(visibilityState, action) {
// Technically, we don't even care about the previous state
return action.filter
}

// Handler for an entire slice of state ("slice reducer")
const visibilityReducer = createReducer('SHOW_ALL', {
SET_VISIBILITY_FILTER: setVisibilityFilter
})

// Case reducer
function addTodo(todosState, action) {
const newTodos = todosState.concat({
id: action.id,
text: action.text,
completed: false
})

return newTodos
}

// Case reducer
function toggleTodo(todosState, action) {
const newTodos = updateItemInArray(todosState, action.id, todo => {
return updateObject(todo, { completed: !todo.completed })
})

return newTodos
}

// Case reducer
function editTodo(todosState, action) {
const newTodos = updateItemInArray(todosState, action.id, todo => {
return updateObject(todo, { text: action.text })
})

return newTodos
}

// Slice reducer
const todosReducer = createReducer([], {
ADD_TODO: addTodo,
TOGGLE_TODO: toggleTodo,
EDIT_TODO: editTodo
})

// "Root reducer"
const appReducer = combineReducers({
visibilityFilter: visibilityReducer,
todos: todosReducer
})

现在我们有多种拆分后的 reducer 函数示例:如 updateObjectcreateReducer 等工具函数,如 setVisibilityFilteraddTodo 等特定 case 处理器,以及如 visibilityReducertodosReducer 等状态切片处理器。同时可见 appReducer 是"根 reducer"的典型示例。

虽然本例最终结果明显长于原始版本,但这主要源于工具函数的抽离、注释的添加以及为清晰性刻意保留的冗余(如独立返回语句)。单独观察每个函数,其职责范围已缩小,设计意图更加明确。在实际应用中,这些函数通常会拆分为独立文件,如 reducerUtilities.jsvisibilityReducer.jstodosReducer.jsrootReducer.js