主要组成
通过前面的介绍可以发现 Redux 应用的主要组成有 action、reducer 和 store。下面借助 todos 这个示例详细地绍这三部分。
action
action 是 Redux 中信息的载体,是 store 唯一的信息来源。把 action 发送给 store 必须通过 store 的 dispatch 方法。action 是普通的 JavaScript 对象,但每个 action 必须有一个 type 属性描述 action 的类型,type 一般被定义为字符串常量。除了 type 属性外,action 的结构完全由自己决定,但应该确保 action 的结构能清晰地描述实际业务场景。一般通过 action creator 创建 action,action creator 是返回 action 的函数。例如,下面是一个新增待办事项的 action creator:
function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
todos 应用涉及的操作有新增待办事项、修改待办事项的状态(已完成/未完成)、筛选当前显示的待办事项列表。对应的完整 action creator 如下:
// actions.js
// action types
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
// 筛选待办事项列表的条件
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
// action creators
// 新增待办事项
export function addTodo(text) {
return { type: ADD_TODO, text}
}
// 修改某个待办事项的状态,index 是待办事项在 todos 数组中的位置索引
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
// 筛选当前显示的待办事项列表
export function setVisiblilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
reducer
action 用于描述应用发生了什么操作,reducer 则根据 action 做出响应,决定如何修改应用的状态 state。既然是修改 state,那么就应该在编写 reducer 前设计好 state。state 既可以包含服务器端获取的数据,也可以包含 UI 状态,前面已经设计了 todos 应用的 state:
{
"todos": [{
"text": 'Learn React',
"completed": true
}, {
"text": 'Learn Redux',
"completed": false
}],
"visibilityFilter": 'SHOW_COMPLETED'
}
有了 state,我们再为 state 编写 reducer。reducer 是一个纯函数,它接收两个参数,当前的 state 和 action,返回新的 state。reducer 函数签名如下:
(previousState, action) => newState
我们先来创建一个最基本的 reducer:
import { VisibilityFilters } from './actions'
const initialState = {
todos: [],
visibilityFilter: VisibilityFilters.SHOW_ALL
}
// reducer
function todoApp(state = initialState, action) {
return state
}
todoApp 这个 reducer 不做任何事情,对于任意 action 做出的响应都是直接返回前一个 state。这里需要注意 state 初始值的设置,当 todoApp 第一次被调用时,state 等于 undefined,这时会用 initialState 初始化 state。现在为 todoApp 添加处理 type 等于 SET_VISIBILITY_FILTER 的 action,要做的事情是改变 state 的 visibilityFilter:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return { ...state, visibilityFilter: action.filter }
default:
return state
}
}
注意,这里使用 ES6 的扩展运算符(…)创建新的 state 对象,避免直接修改之前的 state 对象。还有一种常见的写法是使用 ES6 的 Object.assign() 函数:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
下面再来处理另外两个 action,同样需要保证每次返回的 state 对象都是一个新的对象:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
// 新增待办事项
case ADD_TODO:
// 使用了 ES6 的扩展语法
return { ...state,
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
}
// 修改待办事项的状态(已完成/未完成)
case TOGGLE_TODO:
return { ...state,
todos: state.todos.map((todo, index) => {
if(index === action.index) {
return { ...todo, completed: !todo.completed }
}
return todo
})
}
default:
return state
}
}
当前,我们使用 todoApp 一个 reducer 处理所有的 action,当应用变得复杂时,这个 reducer 也会逐渐变复杂,这时,一般会拆分出多个 reducer,每个 reducer 处理 state 中的部分状态。例如,这里可以拆分出 todos 和 visibilityFilter 两个 reducer,分别处理 state 的 todos 和 visibilityFilter 两个子状态:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index
? {...todo, completed: !todo.completed}
: todo
)
default:
return state
}
}
// 处理 visibilityFilter 的 reducer
function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
// todoApp 简化为:
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
注意,每个拆分的 reducer 只接收它负责的 state 中的部分属性,而不再是完整的 state 对象。todos 接收 state.todos,visibilityFilter 接收 state.visibilityFilter。这样,当应用较复杂时,就可以拆分出多个 reducer 保存到独立的文件中。
Redux 还提供了一个 combineReducers 函数,用于合并多个 reducer。使用 combineReducers,todoApp 可以改写如下:
import { combineReducers } from 'redux'
const todoApp = combineReducers({
todos,
visibilityFilter
})
它等价于:
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
还可以为 combineReducers 接收的参数对象指定和 reducer 的函数名不同的 key 值:
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)
}
}
可见,combineReducers 传递给每个 reducer 的 state 中的属性取决于它的参数对象的 key 值。
store
store 是 Redux 中的一个对象,也是 action 和 reducer 之间的桥梁。store 主要负责以下几个工作:
-
保存应用状态。
-
通过方法 getState() 访问应用状态。
-
通过方法 dispatch(action) 发送更新状态的意图。
-
通过方法 subscribe(listener) 注册监听函数、监听应用状态的改变。
一个 Redux 应用中只有一个 store,store 保存了唯一数据源。store 通过 createStore() 函数创建,创建时需要传递 reducer 作为参数,创建 todos 应用的 store 的代码如下:
import { createStore } from 'redux'
import todoApp from './reducers'
let stroe = createStore(todoApp)
创建 store 时还可以设置应用的初始状态:
// initialState 代表初始状态
let store = createStore(todoApp, initialState)
除了可以在创建 store 时设置应用的初始状态外,还可以在创建 reducer 时设置应用的初始状态,例如:
// 初始状态是一个空数组
function todos(state = [], action) {
//...
}
// 初始状态等于 SHOW_ALL
function visibilityFilter(state = 'SHOW_ALL', action) {
//...
}
todos 设置的初始状态是 state = [],visibilityFilter 设置的初始状态是 state ='SHOW_ALL',这样,当把这两个 reducer 合并成一个 reducer 时,两个 reducer 的初始状态就构成了整个应用的初始状态:
{
todos: [],
visibilityFilter: 'SHOW_ALL'
}
store 创建完成后,就可以通过 getState() 获取当前应用的状态 state:
const state = store.getState()
当需要修改 state 时,通过 store 的 dispatch 方法发送 action。例如,发送一个新增待办事项的 action:
// 定义 action
function addTodo(text) {
return {type: 'ADD_TODO', text}
}
// 发送 action
store.dispatch(addTodo('Learn about actions'))
当 todoApp 这个 reducer 处理完成 addTodo 这个 action 时,应用的状态会被更新,此时通过 store.getState() 可以得到最新的应用状态。为了能准确知道应用状态更新的时间,需要向 store 注册一个监听函数:
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
这样,每当应用状态更新时,最新的应用状态就会被打印出来。当需要取消监听时,直接调用 store.subscribe 返回的函数即可:
unsubscribe()
下面再来总结一下 Redux 的数据流过程。
-
调用 store.dispatch(action)。一个 action 是一个用于描述 “发生了什么” 的对象。store.dispatch(action) 可以在应用的任何地方调用,包括组件、XHR 的回调,甚至在定时器中。
-
Redux 的 store 调用 reducer 函数。store 传递两个参数给 reducer:当前应用的状态和 action。reducer 必须是一个纯函数,它的唯一职责是计算下一个应用的状态。
-
根 reducer 会把多个子 reducer 的返回结果组合成最终的应用状态。根 reducer 的构建形式完全取决于用户。Redux 提供了 combineReducers,方便把多个拆分的子 reducer 组合到一起,但完全可以不使用它。当使用 combineReducers 时,action 会传递给每一个子 reducer 处理,子 reducer 处理后的结果会合并成最终的应用状态。
-
Redux 的 store 保存根 reducer 返回的完整应用状态。此时,应用状态才完成更新。如果 UI 需要根据应用状态进行更新,那么这就是更新 UI 的时机。对于 React 应用而言,可以在这个时候调用组件的 setState 方法,根据新的应用状态更新 UI。