2021年的React状态管理 (opens new window)
# Redux
# redux 的工作流程?
首先,我们看下几个核心概念:
- Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个 Store。
- State:Store 对象包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State。
- Action:State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
- Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦,所以我们定义一个函数来生成 Action,这个函数就叫 Action Creator。
- Reducer:Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
- dispatch:是 View 发出 Action 的唯一方法。
然后我们过下整个工作流程:
- 首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法。
- 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
- State 一旦有变化,Store 就会调用监听函数,来更新 View。
到这儿为止,一次用户交互流程结束。可以看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。
# react-redux 是如何工作的?
- Provider: Provider 的作用是从最外部封装了整个应用,并向 connect 模块传递 store
- connect: 负责连接 React 和 Redux
- 获取 state: connect 通过 context 获取 Provider 中的 store,通过 store.getState()获取整个 store tree 上所有 state
- 包装原组件: 将 state 和 action 通过 props 的方式传入到原组件内部 wrapWithConnect 返回一个 ReactComponent 对象 Connect,Connect 重新 render 外部传入的原组件 WrappedComponent,并把 connect 中传入的 mapStateToProps, mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent
- 监听 store tree 变化: connect 缓存了 store tree 中 state 的状态,通过当前 state 状态和变更前 state 状态进行比较,从而确定是否调用
this.setState()
方法触发 Connect 及其子组件的重新渲染
# redux 与 mobx 的区别?
两者对比:
- redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中
- redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作
- redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改
- mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
- mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
场景辨析:
基于以上区别,我们可以简单得分析一下两者的不同使用场景.
mobx 更适合数据不复杂的应用: mobx 难以调试,很多状态无法回溯,面对复杂度高的应用时,往往力不从心.
redux 适合有回溯需求的应用: 比如一个画板应用、一个表格应用,很多时候需要撤销、重做等操作,由于 redux 不可变的特性,天然支持这些操作.
mobx 适合短平快的项目: mobx 上手简单,样板代码少,可以很大程度上提高开发效率.
当然 mobx 和 redux 也并不一定是非此即彼的关系,你也可以在项目中用 redux 作为全局状态管理,用 mobx 作为组件局部状态管理器来用.
# redux 中如何进行异步操作?
当然,我们可以在componentDidmount
中直接进行请求无须借助 redux.
但是在一定规模的项目中,上述方法很难进行异步流的管理,通常情况下我们会借助 redux 的异步中间件进行异步处理.
redux 异步流中间件其实有很多,但是当下主流的异步中间件只有两种 redux-thunk、redux-saga,当然 redux-observable 可能也有资格占据一席之地,其余的异步中间件不管是社区活跃度还是 npm 下载量都比较差了.
# redux 异步中间件之间的优劣?
redux-thunk 优点:
- 体积小: redux-thunk 的实现方式很简单,只有不到 20 行代码
- 使用简单: redux-thunk 没有引入像 redux-saga 或者 redux-observable 额外的范式,上手简单
redux-thunk 缺陷:
- 样板代码过多: 与 redux 本身一样,通常一个请求需要大量的代码,而且很多都是重复性质的
- 耦合严重: 异步操作与 redux 的 action 偶合在一起,不方便管理
- 功能孱弱: 有一些实际开发中常用的功能需要自己进行封装
redux-saga 优点:
- 异步解耦: 异步操作被被转移到单独 saga.js 中,不再是掺杂在 action.js 或 component.js 中
- action 摆脱 thunk function: dispatch 的参数依然是一个纯粹的 action (FSA),而不是充满 “黑魔法” thunk function
- 异常处理: 受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理
- 功能强大: redux-saga 提供了大量的 Saga 辅助函数和 Effect 创建器供开发者使用,开发者无须封装或者简单封装即可使用
- 灵活: redux-saga 可以将多个 Saga 可以串行/并行组合起来,形成一个非常实用的异步 flow
- 易测试,提供了各种 case 的测试方案,包括 mock task,分支覆盖等等
redux-saga 缺陷:
- 额外的学习成本: redux-saga 不仅在使用难以理解的 generator function,而且有数十个 API,学习成本远超 redux-thunk,最重要的是你的额外学习成本是只服务于这个库的,与 redux-observable 不同,redux-observable 虽然也有额外学习成本但是背后是 rxjs 和一整套思想
- 体积庞大: 体积略大,代码近 2000 行,min 版 25KB 左右
- 功能过剩: 实际上并发控制等功能很难用到,但是我们依然需要引入这些代码
- ts 支持不友好: yield 无法返回 TS 类型
redux-observable 优点:
- 功能最强: 由于背靠 rxjs 这个强大的响应式编程的库,借助 rxjs 的操作符,你可以几乎做任何你能想到的异步处理
- 背靠 rxjs: 由于有 rxjs 的加持,如果你已经学习了 rxjs,redux-observable 的学习成本并不高,而且随着 rxjs 的升级 redux-observable 也会变得更强大
redux-observable 缺陷:
- 学习成本奇高: 如果你不会 rxjs,则需要额外学习两个复杂的库
- 社区一般: redux-observable 的下载量只有 redux-saga 的 1/5,社区也不够活跃,在复杂异步流中间件这个层面 redux-saga 仍处于领导地位
关于 redux-saga 与 redux-observable 的详细比较可见此链接 (opens new window)
# Mobx
# DvaJS
# Recoil
Recoil实现原理浅析-异步请求 (opens new window)