概述
Redux 本身是个极其简单的状态管理框架, 它的简单体现在概念少, 流程明确. 但是, 正是因为简单, 使用上没有强制的约束, 所以如果用不好, 反而会让状态管理更加混乱.
我觉得, 用好 Redux, 首先必须了解其中的几个基本概念, 不只是看看文档, 了解它们的定义, 关键是理解其和整个状态管理的关系. 其次, 要能将这些概念对应到具体的业务系统中, 通过这些概念来规划业务系统的状态管理.
Redux 核心和原则
Redux 核心或者说目的一句话就能概括, 清晰的描述应用的状态 . Redux 提供的 API 没有什么神奇之处, 只是为了实现这个核心而提供的一些工具而已, 不用这些 API 一样能实现 Redux 的状态管理.
Redux 提出的所谓 3 条原则, 其实并不是框架中的约束, 框架对于你如何组织代码其实是极其自由的. 这 3 条原则, 是在自己组织状态管理时, 需要时时记在心里, 自己来控制代码不要违反原则.
- 这个应用的状态是一个唯一的状态树
- 状态是只读的, 只能通过 action 来触发修改, 其实实际修改状态的是 reducer
- 修改状态只能通过纯函数
Redux 中的概念
了解 Redux 中的基本概念, 有助于组织出符合 redux 风格的状态管理, 仅仅使用 redux API 来管理状态, 并不是真正的 redux. Redux 中的概念主要有:
- state
- reducer
- action
其他都是基于这 3 个概念衍生出来的, 这 3 个概念和 Redux 的状态处理流程息息相关.
data flow
严格的单向数据流 , 整个 Redux 都是围绕它来组织的.
单向的数据流, 明确每次 state 的改变, 确保整个应用的状态变化清晰, 可追溯.
reducer
reducer 就是实际改变 state 的函数, 在 redux 中, 也只有 reducer 能够改变 state. 这时再看 redux 的 3 个原则, 我们在组织代码时必须确保 reduer 是纯函数.
根据 redux 的原则, 整个应用只有一个唯一的状态树, 这样, 理论上只要一个 reducer 就够了. 但是, 实际编码时, 如果应用稍具规模, 只有一个 reducer 文件, 显然不利于分模块合作开发, 也不利于代码维护.
所以, reduer 一般是按模块, 或者根据你所使用的框架来组织, 会分散在多个文件夹中. 这时, 可以通过 redux 提供的 API combineReducers 来合并多个 reducer, 形成一个唯一的状态树.
reducer 的使用只要注意 2 点:
- 必须是纯函数
- 多个 reducer 文件时, 确保每个 reducer 处理不同的 action, 否则可能会出现后面的 reducer 被覆盖的情况
state
state 或者说是 store, 其实就是整个应用的状态. 其中, 哪些内容是 state? 哪些不是? 哪些要放在 state 中管理? 哪些由页面自己管理? 等等是关键. state 定义的好坏直接影响了应用的维护性和扩展性, 反而是用哪种状态管理框架无关紧要.
很多应用其实是用了很前沿的状态管理技术或者框架, 花了很多时间去熟悉框架的使用, 理解框架的概念, 但是却没有好好定义应用的状态, 被管理的状态如果本身就乱, 管理的再好也没用.
一般, 状态管理框架都有个 计算属性 的概念, redux 也有, 计算属性 在 redux 中的实现有 2 种方式,
- 通过 reducer, 定义一些计算属性(本质上还是 state), 在改变 state 的时候同时修改这些计算属性.
- 通过 selector, 这个是专门用于 redux 库的 selector. reselect
第二种方案是比较通用的, 也是后面示例中使用的方式
action
redux 中的 action 其实就是一个 包含 type 字段的 plain object. type 字段决定了要执行那个 reducer, 其他字段作为 reducer 的参数.
action creator
action creator 本质是一个函数, 返回值是一个满足 action 的定义的 plain object. 使用 action creator 的目的就是简化 action 的定义, 比如:
const action1 = { type: 'CONNECT', palyload: {user: 'xxx', passwd: 'yyy'}}
const action2 = { type: 'CONNECT', palyload: {user: 'zzz', passwd: 'aaa'}}
这种情况下, 就可以用 action creator 来简化:
const actionCreator = (payload) => ({type: 'CONNECT', payload})
async action
redux 本身的 action 都是是同步的, 但是可以通过如下方法完成异步操作.
const asyncAction = (payload, dispatch) => {
fetchAPI('xxx_url')
.then(result => dispatch({type: 'SUCCESS', payload: result}))
.catch(e => dispatch({type: 'FAILED', error: e}))
}
// call async action
asyncAction(payload, store.dispatch)
这样虽然可以完成异步的操作, 但是使用上看起来已经不是 redux 风格的 action 了. 为了保持同步和异步的一致性, 可以通过 middleware 的方式来让 redux 支持异步的 action.
middleware
redux 的 middleware 发生在 dispatching an action 和 reaches the reducer 之间. 在这个时间点, 除了可以实现异步操作, 还可以实现 logging, 路由, 崩溃报告等等.
用 middleware(redux-thunk) 改造上面的异步操作, 让其看起来和其他 redux action 一样.
// createStore 时 applyMiddleware
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
rootReducer,
applyMiddleware(thunk)
)
const asyncAction = (payload) => {
return dispatch => {
fetchAPI('xxx_url')
.then(result => dispatch({type: 'SUCCESS', payload: result}))
.catch(e => dispatch({type: 'FAILED', error: e}))
}
}
// call asyncAction
store.dispatch(asyncAction(payload))
asyncAction 的功能和上面一样, 只是使用上更像 redux. redux-thunk middleware 没有什么神奇之处, 它所做的事情就是在 dispatch(function) 的时候, 把 dispatch 作为参数传给 function. 不用 redux-thunk, 只能 dispatch(object), 这里的 object 是描述 action 的 plain object.
Redux 示例(包含计算属性)
完整的示例参见: redux-sample
action:
export const CHANGE_NUM = 'CHANGE_NUM'
export const actionChangeNumCreator = num => {
return {
type: CHANGE_NUM,
num
}
}
reducer:
import { CHANGE_NUM } from '../actions/action'
export const multiReducer = (state = { num: 1 }, action) => {
switch (action.type) {
case CHANGE_NUM:
return { num: action.num }
default:
return state
}
}
selector:
import { createSelector } from 'reselect'
export const mult2Num = createSelector(
state => state.num,
num => {
return num * 2
}
)
export const mult3Num = createSelector(
state => state.num,
num => {
return num * 3
}
)
UI 部分:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { mult2Num, mult3Num } from './selectors/selector'
import { actionChangeNumCreator } from './actions/action'
import { Input } from 'antd'
import './App.css'
class App extends Component {
render() {
const { num, num2, num3, changeNum } = this.props
return (
<div className="App">
<Input defaultValue={num} onChange={changeNum} />
<p>num*2 = {num2}</p>
<p>num*3 = {num3}</p>
</div>
)
}
}
const mapStateToProps = state => {
return {
num: state.num,
num2: mult2Num(state),
num3: mult3Num(state)
}
}
const mapDispatchToProps = dispatch => {
return {
changeNum: e => {
dispatch(actionChangeNumCreator(e.target.value))
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
补充-thunk 介绍
thunk 的概念来源于函数式编程, thunk 本身其实就是一个函数, 这个函数会在某个其他函数执行时才执行. thunk 最主要的用途就是延迟某些操作的执行.
redux-thunk middleware 也就是插入一个函数, 这个函数在 dispatch(function)时执行. 上面 asyncAction 的例子中, 这个 thunk 函数就是:
dispatch => {
fetchAPI('xxx_url')
.then(result => dispatch({type: 'SUCCESS', payload: result}))
.catch(e => dispatch({type: 'FAILED', error: e}))
}