react 的范式为 UI = render(state)
,用户的界面完全取决于数据层。react 中通过管理状态(state)来实现对组件的管理,当 state 发生变更后,react 就会重新渲染界面,组件与组件之间也经常需要共享状态。如果缺乏一个好的状态管理方案,那么共享数据将变得麻烦,同时状态不受控的话会让我们很难跟踪调试程序。
react 本身采用的时自上而下的单向组件数据流,我们通常将代码抽成 Smart Component 组件和 Dumb Component 组件。通过 proprs 来连接,来完成功能。针对小的项目,这就足够了,但是项目大了以后会出现如下几个问题:
- 如何跨组件实现状态同步
react 16 提供新的的 context 可以解决这一问题,但是 context 一般会放顶级组件上,一旦有改变将触发所有组件的re-render,这将带来损耗。 - 如何让状态变得可预知,甚至可回溯
- 如何避免组件臃肿,Model 和 View 都混在了一起
为此前端提出了一个通用解决思路:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。针对这个思路,我们来看下都有哪些实现。 (隔离变化,约定大于配置,其实不管是 Vue,还是 React 都对其状态管理有着同样的要求)
单向数据流体系
Store 模式
我们将状态存在一个全局变量 store
里面,store 里面设置一些方法来控制 state 的改变。约定
:组件只能通过调用 store 上的方法来改变数据,而不能直接操作 store 里面的 state。这样就保证了数据的可追溯。
|
|
进一步,为了方便管理 view 层调用相应的 store 方法,我们包装一个 dispatcher
来映射 view 层的一个动作 action 到 store 上。这样一个 flux 架构就诞生了。
Flux
Flux 本身是一种思想,一种约定。Flux 把一个应用分成 4 个部分:View, Action, Dispatcher, Store
View 层用来展示数据,当用户操作UI上的某个操作,就会触发 Dispatcher。
Dispatcher 就像一个请求中转站一样会 dispatch 一个 action 给 Store。Store 根据这个 action 来改变数据。当然 Action 也可以由其他地方触发。
一旦 Store 发生了变化,就会往外面发送一个事件,比如 change,来通知所有的订阅者。View 会监听这个事件,从而触发自身的 re-render。(实现:发布订阅模式)
Dispatcher 的作用是接收所有的 Action,然后发给所有的 Store。 Store 的改变只能通过 Action,不能通过其他方式。
改写上面的例子:
store 部分加入发布订阅
|
|
Dispatcher 部分
|
|
View 层
|
|
可以看出,Flux 核心思想就是数据都是单向流动的。
Flux 本身会有很多个 store 来存储引用数据,并在各自的 store 里面执行更新逻辑。那么当多个store之间有依赖关系的时候,就不太好处理。同时 store 里面不仅封装了数据,还有处理数据的逻辑。
Redux
在 Flux 的基础上,Redux 对其进行了一些改进增强。
在 Redux 中没有 Dispatcher 的概念,它使用 reducer 来进行事件处理。
reducer 是一个纯函数,每个 reducer 负责维护应用整体 state 树中的某一部分,多个 reducer 可以通过 combineReducers 方法合成一个根reducer,这个根reducer负责维护完整的 state。约定
: reducer 必须为纯函数(此函数在相同的输入值时,需产生相同的输出。函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O 设备产生的外部输出无关,例如 Ajax)。
由于有这样的规定,redux 成功副作用隔离。我们很容易判断出数据的变化的原因,数据流清晰可回朔。同时 Redux 引入的 immutable 进一步隔离了对象引用的问题。
对比 Flux
Redux 和 Flux 之间最大的区别就是对 store/reducer 的抽象,Flux 中 store 是各自为战的,每个 store 只对对应的 controller-view 负责,每次更新都只通知对应的 controller-view;而 Redux 中各子 reducer 都是由根reducer统一管理的,每个子reducer的变化都要经过根reducer的整合。
综上:Redux有三大原则:
- 单一数据源:Flux 的数据源可以是多个。
- State 是只读的:Flux 的 State 可以随便改。
- 使用纯函数来执行修改:Flux 执行修改的不一定是纯函数。
Redux 异步
在 Redux 中,每当我们发出一个 Action,Reducer 就会立即算出 State。那么我们想支持异步呢?在哪儿加入异步操作呢?
Reducer ? 纯函数,不能引入IO,不适合。Action ? 一个纯对象,没有位置。所以只能通过包装 dispatch 加上中间件的动能。例如:
|
|
Redux 提供了一个 applyMiddleware 方法来应用中间件:
|
|
这个方法主要就是把所有的中间件组成一个数组,依次执行。也就是说,任何被发送到 store 的 action 现在都会经过thunk,promise,logger 这几个中间件了。
关于 Redux 的异步处理,之后会有单独解析 redux-sage 的内容。
Mobx
之前说的都是单项数据流方案,思想主要就是函数是编程(FP)的思想。Mobx 则是一个 TFRP 的框架,FRP 的一个分支。
mobx的流程图如上,通常是:触发action,在action中修改state,通过computed拿到state的计算值,自动触发对应的reactions,这里包含autorun,渲染视图等。有一点需要注意:相对于react来说,mobx没有一个全局的状态树,状态分散在各个独立的 store 中。这种自动订阅,自动发布的模式,使得开发十分方便。
但是相对的,mobx 中并没有解决副作用问题,同时,对 props 的直接修改,也会导致与 react 对 props 的不可变定义冲突。因此 mobx 后来给出了 action 解决方案,解决了与 react props 的冲突,但是没有解决副作用未强制分离的问题。
对比一下:
- redux 采用全局单一 store,mobx 则由多个独立 store 组成
- redux 通过 action 将副作用隔离在 reducer 之外。而 mobx 比较自由,没有对副作用进行处理。
- redux 函数式、不可变、模式化;mobx 响应式、依赖追踪
- redux 开发需要些很多样板代码,但是调试数据的时候确很方便。mobx 书写简单,但是没有强约束换来的是调试困难。