Actions 描述了在应用里面发生的事情,但是应用的状态(state)具体应该怎么样响应这些动作是 Reducers 的任务。
设计状态
state(状态),指的就是数据。在 Redux 里,应用的所有的状态都会存储在唯一的一个对象里。写代码之前最好先想想这个对象的形状。
比如我们的任务列表应用,会存储两种东西:
- 当前所选的可见性过滤器
- 任务列表项目
在状态树里会存储数据还有一些 UI(界面) 状态,最好让数据与 UI 状态分开。
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
处理动作
我们已经确定了状态对象的样子,现在可以去为它写个 Reducer 了。Reducer 是纯函数(Pure functions),Reducer 会接收两个东西:之前的状态还有一个动作,然后它会返回应用的下一个状态。
(previousState, action) => newState
Reducer 是纯函数,下面这些东西不应该在 Reducer 里出现:
- 改变了它的参数。
- 做了些带副作用的事,比如调用了 API。
- 调用不纯的函数,比如 Date.now() 或 Math.random() 。
以后会介绍怎么样做带副作用的事,现在你要记住的是 Reducer 必须是纯的。给它同样的参数,它要运算并返回下一个状态。无意外,不带副作用,不调用 API,没有改变,只是一个计算。
下面我们就一步一步的教会 Reducer 明白我们之前定义的动作。首先我们要定义一个初始的状态。Redux 第一次调用 Reducer 会带一个 undefined 状态,返回应用的初始状态:
import { VisibilityFilters } from './actions'
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
function todoApp(state, action) {
if (typeof state === 'undefined') {
return initialState
}
// For now, don't handle any actions
// and just return the state given to us.
return state
}
也可以使用 ES2015 函数的默认参数,像这样:
function todoApp(state = initialState, action) {
// For now, don't handle any actions
// and just return the state given to us.
return state
}
再去处理 SET_VISIBILITY_FILTER 动作,它要做的就是改变状态里的 visibilityFilter:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
注意:
- 我们没有直接改变状态,而是创建了一个复制品,这里用了 Object.assign(),也可以使用对象的 Spread 操作符: { ...state, ...newState }。
- 在默认的情况下返回之前的状态。这样如果有未知的动作发生的时候,就会返回之前的状态。
处理更多动作
还有两个要处理的动作,跟处理 SET_VISIBILITY_FILTER 动作一样。下面我们要导入 ADD_TODO 还有 TOGGLE_TODO 动作,然后再去改造一下我们的 Reducer。
function todoApp(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,
{
text: action.text,
completed: false
}
]
})
default:
return state
}
}跟之前一样,不要直接改变状态,要返回一个新的对象,新的任务列表就是老的任务列表加上新的任务列表项目,新的任务列表项目用了在动作那里组织好的数据。
再处理下 TOGGLE_TODO:
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
})
分离 Reducers
代码现在是这样的:
function todoApp(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,
{
text: action.text,
completed: false
}
]
})
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => {
if(index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
})
default:
return state
}
}
有时候状态字段之间会相互依赖。不过我们的应用比较简单,todos(任务列表) 与 visibilityFilter(可见性过滤器) 是完全独立的,所以可以很容易分离开,下面把更新 todos 的东西分离出来:
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
}
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: todos(state.todos, action)
})
default:
return state
}
}注意上面的 todos 同样会接收 state(状态),不过这个 state 是一个数组。todoApp 只给它一部分状态去管理,todos 知道怎么样去更新这小部分状态。这就是 Reducer 组合,这是创建 Redux 应用的基础模式。
下面再抽离出一个 visibilityFilter :
const { SHOW_ALL } = VisibilityFilters;
然后再:
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
重新再写一下主 Reducer(todoApp),用它组合一下之前我们抽离出来的两个 Reducer(visibilityFilter 与 todos):
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
}
}
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
注意每个 Reducer 都会管理它们自己的那部分全局状态。每个 Reducer 的 state 参数是不一样的,对应的就是它管理的那部分 state。你也可以把 Reducer 放在单独的文件里。
Redux 提供了一个帮手方法:combineReducers(),用它可以这样重写一下组合 Reducer 的代码:
import { combineReducers } from 'redux'
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
上面的代码跟下面这块代码的功能是一样的:
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
也可以给 Reducer 起个不同的名字,下面这两种方法的效果是一样的:
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
Source Code
reducers.js
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
Redux 


