Reducer
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如
Date.now()
或Math.random()
。
在后续笔记里会介绍如何执行有副作用的操作。现在只需要谨记 reducer 一定要保持纯净。只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
注意:
-
不要修改
state
。 使用Object.assign()
新建了一个副本。不能这样使用Object.assign(state, { visibilityFilter: action.filter })
,因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用{ ...state, ...newState }
达到相同的目的。 -
在
default
情况下返回旧的state
。遇到未知的 action 时,一定要返回旧的state
。 -
定义一个initialState变量,当state的初始值
拆分 Reducer
示例:
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state
参数都不同,分别对应它管理的那部分 state 数据。
随着应用的膨胀,我们还可以将拆分后的 reducer 放到不同的文件中, 以保持其独立性并用于专门处理不同的数据域。
Redux 提供了 combineReducers()
combineReducers()
所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。正如其他 reducers,如果 combineReducers() 中包含的所有 reducers 都没有更改 state,那么也就不会创建一个新的对象。
combineReducers
接收一个对象,可以把所有顶级的 reducer 放到一个独立的文件中,通过 export
暴露出每个 reducer 函数,然后使用 import * as reducers
得到一个以它们名字作为 key 的 object:
import { combineReducers } from 'redux'
import * as reducers from './reducers'
const todoApp = combineReducers(reducers)
combineReducers(reducers)
随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。
combineReducers
辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore
方法。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers()
返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers()
时对应的 key 进行命名。
示例:
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// rootReducer 将返回如下的 state 对象
{
potato: {
// ... potatoes, 和一些其他由 potatoReducer 管理的 state 对象 ...
},
tomato: {
// ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 对象,比如说 sauce 属性 ...
}
}
通过为传入对象的 reducer 命名不同的 key 来控制返回 state key 的命名。例如,你可以调用 combineReducers({ todos: myTodosReducer, counter: myCounterReducer })
将 state 结构变为 { todos, counter }
。
每个传入 combineReducers
的 reducer 都需满足以下规则:
-
所有未匹配到的 action,必须把它接收到的第一个参数也就是那个
state
原封不动返回。 -
永远不能返回
undefined
。当过早return
时非常容易犯这个错误,为了避免错误扩散,遇到这种情况时combineReducers
会抛异常。 -
如果传入的
state
就是undefined
,一定要返回对应 reducer 的初始 state。根据上一条规则,初始 state 禁止使用undefined
。使用 ES6 的默认参数值语法来设置初始 state 很容易,但你也可以手动检查第一个参数是否为undefined
。