redux
what’s redux
redux 中文文档 中的几个关键特性:
- 状态容器,提供可预测化的状态管理
- 跨平台,客户端、服务端、原生应用都能用
- 易于测试
- 轻量,支持 react 等界面库
其中第一点,讲明了 redux 的主要用途:状态容器
以 react 为例,页面是渲染状态树得到,有静态状态(props)、动态状态(state)。通过在代码中 setState 来修改状态树,状态自上而下传递到各个子组件,最终触发组件树的重新渲染。
使用 redux,我们就将状态的定义和允许的迁移 function 挪出去,放到一个状态容器里,来有效管理组件的所有状态和状态迁移。此时 component 代码中就没有 state、setState 等定义了。
counter without redux
以计数器为例,不使用 redux,即直接在本地定义 state,并通过 setState 修改状态,以实现重现渲染。(源代码)
1 | import React, {Component} from 'react'; |
counter with redux
如果使用 redux,就是将原本的 state 存储到一个 store 中,通过 dispatch 一个 action(eg. {type: 'INCREMENT'})触发状态变化,action 如何改变 state 是由一个纯函数 reducer 定义的。(源代码)
1 | // counter.js |
why redux
不使用 redux 时,
- 状态(model)、状态如何被改变(controller)、页面模板/html(view)是耦合在一块的。(react 提供的就是一种界面库,render 函数其实写的就是页面模板,将 model 填充进去,渲染得到最后的 view)
- 组件之间强耦合,难以确定状态的改变是在哪里被谁触发,bug 追踪难。
使用 redux 后,
- model 被抽出来了(即当前视图所对应的 model 对象),而且这个 model 对象不是一个贫血模型,它提供基本的 action 来保证 model 的完整性。
- 由于状态的变更只能通过
dispatch来触发,解耦了父子组件之间的状态变更传递,易于定位. (可以利用 middleware 记录 state 变更日志,即可实现 state 变化过程透明和可预测)
核心概念
redux 是一个状态容器,它解决了:
- 状态在哪:
createStore() - 状态是什么:
store.getState() - 状态怎么变: reducer,即
(preState, action) => nextState函数 - 触发状态变更:
store.dispatch(action) - 谁关心状态变化:
store.subscribe(callback)
reducer
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。reducer 函数形式如下:
1 | (preState, action) => nextState |
因为 state 有很多属性,针对属性的处理也有很多,全放在一块就太大、难管理,所以可以拆 reducer(称为 slice reducer),再利用 combineReducer 组合。(redux 总是一个根,多个子:一个 store,多个子状态;一个根 reducer,多个 slice reducers)
使用 combineReducer 后有两个问题:
- store state 和 slice reducer state 的关系?
- slice reducer 如何被调用执行?
store state 和 slice reducer state 的关系?
combineReducers()返回的state对象,会将传入的每个 reducer 返回的state按其传递给combineReducers()时对应的 key 进行命名,并将所有 slice reducer 返回的结果合并
举例:
1 | const reducer1 = (state='initA', action) => {...} |
slice reducer 如何被调用执行?
返回值:
(Function):一个调用 reducers 对象里所有 reducer 的 reducer,并且构造一个与 reducers 对象结构相同的 state 对象。
即 combinedReducer 会调用执行所有的 slice reducer,以实现分层处理
初始化 state
有两种方式:
- 使用
createStore(reducers, [preloadedState], [enhancers])中的preloadedState - 使用 reducer 中的默认属性(
function someReducer(state={defaultValue}, action){})
这其中有两条规则:
- preloadedState 先于 reducer 去填充 state
- 创建 store 后,redux 会
dispatch一个虚拟的action到 reducer,以触发其中的默认值来填充 state。
使用单一简单 reducer
使用
preloadedState后,单一 reducer 的默认 state 赋值会失效,因为流程是这样的:
- 创建 store,并根据
preloadedState填充state; dispatch虚拟action来使用 reducer 默认值填充state。而此时state已经不是undefind,因此,这个默认值填充是无效的
举一个例子:
1 | // reducer |
使用 combineReducer()
使用
preloadedState后,使用了combineReducers的 reducer 默认 state 赋值可能会失效,因为流程是这样的:
- 创建 store,并根据
preloadedState填充state; dispatch虚拟action来调用所有子 reducers,- 如果
preloadedState中定义了当前reducer的属性,则对当前 reducer,state已经不是undefind,此时默认值填充是无效的 - 如果
preloadedState中没有定义当前reducer的属性,则对当前 reducer,state是undefind,默认值填充有效
- 如果
同样看一个例子:
1 | // reducer |
使用
combineReducers时,preloadedState必须包含 slice reducer 中的属性,与传入的 keys 保持同样的结构 (参见 createStore API 说明)
举例:
1 | const reducer1 = (state='initA', action) => {...} |