什么是 redux
Redux 是一个基于 js 的全局可预测状态容器,主要用于现代前端框架中进行全局状态管理,能够在不同组件之间进行状态共享
Redux 常与 React 配合使用,但它并非只能用于 React,由于 Vue 官方推出了自己的状态管理库 Vuex,因此 Redux 很少在 Vue 中使用
Redux 的实现借鉴了 Flux, 如单项数据流。但又有别于 Flux,如全局单例 store
redux 三大原则
-
单一数据源
store 必须是唯一的,全局的 state 存储在单一 store 中 -
state 是只读的
state 只能通过派发 action 来改变 -
reducer 必须是纯函数
reducer 只做一件事情,通过累积的 preState 和当前的 action 计算得出新的 state
相同的输入必须得到相同的结果,因此 reducer 必须是一个纯函数
reducer 中不应该出现副作用,比如发请求
为什么要用 redux
React 的定义是"一个用于构建 UI 界面的 javascript 库",React 关注的点在于如何将状态转换为 UI(UI = fn(state)),在自带的状态管理方案中:
- state: 适用于管理自身状态,也可联合 this.props.chidren 实现 callback render
- props: 适用于父子组件传值,但父子组件嵌套过深时这种方式过于繁琐
- context: 适用于父子组件嵌套过深和兄弟组件共享状态的场景。context 一般是作为局部的状态管理方案而不是全局的,因为 context 一旦更新,Provider 下包裹的所有子组件都会重新渲染,造成性能问题
在一个复杂的应用中,数据的流向存在跨层、反向的数据流, 在交互上也存在父子、兄弟、跨组件通信, 不利于维护
而 Redux 只需要在最外层传入 store, 内层组件即可通过 props 与 store 中的状态交互
在 Redux 中,数据的流动是单向的,store 是全局单一的,reducer 是纯函数,同样的输入得到的输出一定相同,因此状态是可预测的
- 某个组件的状态,需要让其他组件可以随时拿到
- 一个组件需要改变另外一个组件的状态
- 如果你不确定要不要用 redux,那就一定不需要用 redux
redux 基本 api createStore
createStore 是 redux 的重要组成部分, 大部分 api 都基于它生成
以下是 createStore 的形参列表
- reducer: 初始化、更新 state
- preloadedState: 默认初始化 state,但一般不用它而是 reducer 处理 init action 来初始化一个 state
- enhancer: 一般用于 applyMiddleWare 增加中间件,作用是对 createStore 进行增强,覆盖原来的 dispatch 来实现一些功能上的拓展,如异步 action、日志打印、异常监听
getState
获取 state 的唯一方式,返回当前最新的 state
createStore 内部维持了一个变量 currentState,这个变量是私有的,对外部隐藏,只通过唯一接口 getState 对外暴露
react-redux connect 方法底层也是通过这个 api 拿到 state
dispatch
改变 state 的唯一方式,通过 dispatch 将用户的行为以 action 的形式通知给 redux,通知 redux 把计算最新的状态并反馈给用户
如果你想在 dispatch 的前后做一些拓展功能,比如异步 action,异常收集,日志打印,建议使用中间件来做一些增强
subscribe
订阅 store 的状态变化,当且仅当 dispatch 触发状态更新之后执行入参 callback 回调函数,并返回一个取消订阅时执行的回调函数。
通常我们使用这个 api 来实现 redux 和其他 js 库通信
react-redux 底层在 Provider 中通过 store.subscribe 发起订阅,在 state 改变时,会检查子组件是否通过 connect 消费了 store 以及子组件的 props 有变化,如果这两个条件都满足,就使用最新的状态更新子组件,从而达到精确的最小粒度的 render,相对于 context 一旦数据更新就渲染所有 Provider 包裹的子组件而言,这种处理方式在性能上显然是更优的
redux combineReducers 拆分reducer
随着业务复杂度的增加,把所有状态都放在一个 reducer 中进行处理只会让代码变得更加难以维护
此时可以利用 combineReducers 拆分多个不同的 reducer,对不同业务模块的状态进行分别管理
combineReducers 接受一个 reducers obj 作为入参,你可以给不同 reducer 指定 key,这样调用 getState 时拿到的也是对应 key 的 state
如果多个组件要共享 state,使用相同的 reducer 即可
import {combineReducers, createStore} from 'redux'
import countReducer from './reducer'
import countReducer2 from './count2reducer'
const store = createStore(combineReducers(myCountState:countReducer,myCountState2:countReducer2}))
// store.getState() {myCountState:{...},myCountState2:{...}}
export default store
redux MiddleWare 中间件
如果要记录状态的历史变化,你会怎么做?
我们可能很容易想到只要在 dispatch 前后分别 log 一下就好了
但是一个复杂的大型项目,调用 dispatch 的地方不计其数,上述实现过于臃肿
所以我们应当使用中间件
中间件其实就是对 dispatch 的增强,中间件在 action 到达 store 的前后提供了逻辑插入点,我们可以在上面实现一些异步 action、日志输出、异常检测等功能
使用中间件
通过 applyMiddleWare 传入中间件
const store = createStore(reducer, applyMiddleware(thunk))
redux 数据流向
- 初始化 store,此时 redux 会 dispatch 一个 type 为 init,payload 为 undefined 的 action 并从 reducer 拿到最初的 state
- 组件引入 store 并通过 getState 消费 store 状态,通过 subscribe 订阅 store 状态变化
- 用户动作产生 action,dispatch 派发 action 到 store 并通过 reducer 更新状态。dispatch 的前后如果有中间件会在此时执行中间件相关逻辑
- 状态更新发布之后通知订阅者,订阅者执行注册订阅的回调函数,在回调函数中可以调用 render 将最新的状态展现给用户,并等待用户的下一次动作