使用函数分解和 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
}
}
该函数相当短,但已经变得过于复杂。我们正在处理两个不同的关注领域(过滤与管理我们的待办事项列表),嵌套使得更新逻辑难以阅读,而且并不完全清楚每个地方都在发生什么。
提取实用程序函数
第一步可能是分解一个实用程序函数来返回一个带有更新字段的新对象。还有一个重复的模式,即尝试更新数组中的特定项目,我们可以将其提取到一个函数中
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
接下来,我们可以将每个特定情况拆分为自己的函数
// 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
}
}
现在,每个情况发生的事情都非常清楚。我们还可以开始看到一些模式出现。
按领域分离数据处理
我们的应用程序 Reducer 仍然知道应用程序的所有不同情况。让我们尝试将它们拆分,以便过滤逻辑和待办事项逻辑分开
// 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 语句,因此使用一个函数来创建操作类型到 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)
}
}
按切片组合 Reducer
作为最后一步,我们现在可以使用 Redux 的内置 combineReducers
实用程序来处理我们顶级应用程序 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 函数的示例:辅助实用程序,如 updateObject
和 createReducer
,特定情况的处理程序,如 setVisibilityFilter
和 addTodo
,以及状态切片处理程序,如 visibilityReducer
和 todosReducer
。我们还可以看到 appReducer
是“根 Reducer”的示例。
虽然本例中的最终结果明显比原始版本长,但这主要是因为提取了实用函数、添加了注释,以及为了清晰起见而刻意增加了冗长,例如单独的返回语句。单独查看每个函数,现在每个函数的职责都更小,并且意图也更清晰。此外,在实际应用中,这些函数可能会被拆分成单独的文件,例如 reducerUtilities.js
、visibilityReducer.js
、todosReducer.js
和 rootReducer.js
。