浅析组件化时代的前端状态管理(五):响应式状态管理

当我们踏上 React + Redux这艘大船时, 我们时常会觉得, 我们有时候需要遵循太多React的范式, 也要理解太多组件与状态管理的概念, 特别是 Redux的用法, 很多开发者都觉得很难理解, 写一个简单的逻辑要修改很多个文件, 要写很多感觉很冗余的代码. 大部分开发者只是想修个草坪, 然而要让请个工程队?

每一个React开发者被 “组件更新 + 状态管理” 的概念与 “重复渲染/不渲染” 问题困扰的时候, 脑子肯定会冒出一个想法, 为什么不能像很多MVVM框架一样, 数据变了, UI自动变, 组件在相关的数据改变了之后, 就会自动更新, 其实这有点是响应式的感觉了

这里要推荐两个小众的响应式理念的库, 有兴趣可以了解一下

cycle.js 一个函数式和响应式的 JavaScript 框架,编写可观测代码
rx.js 是一个库,它通过使用 observable 序列来编写异步和基于事件的程序

MobX

MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。MobX背后的哲学很简单:

任何源自应用状态的东西都应该自动地获得。

其中包括UI、数据序列化、服务器通讯,等等。

最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 定义状态并使其可观察
import {observable, action} from 'mobx';
var appState = observable({
count: 0
});

appState.addCount = action(() => {
appState.timer = appState.timer + 1;
});



// 2. 创建视图以响应状态的变化
import {observer} from 'mobx-react';
@observer
class App extends React.Component {
render() {
return (
<button onClick={appState.addCount}>
count: {this.props.appState.count}
</button>
);
}
};


ReactDOM.render(<App/>, document.body);

从上面的demo中可以看到, 在React应用中使用MobX可以说是非常简单

  1. 定义状态并使其可观察, 把 Model 数据用 observable包裹起来.
  2. 创建视图以响应状态的变化, 把 View 组件使用 observer 包裹起来.
  3. 当 Model 的数据发生变化时, Mobx会监听到数据的改变, 然后自动更新组件, 这就是所谓的响应式的理念了

MobX 提供了优化应用状态与 React 组件同步的机制,这种机制就是使用响应式虚拟依赖状态图表,它只有在真正需要的时候才更新并且永远保持是最新的。

MobX最核心的两个点:

  1. observable: 用来包装一个属性为 被观察者
  2. observer: 用来包装一个方法为 观察者

observer包裹后的函数, 最重要的作用就是收集依赖和驱动组件刷新, Mobx中有一个 dependenceManager,这个工具类中管理了一个依赖的 dependencMap
结构是一个全局唯一的 ID 和 对应的监听的函数的数组。

1
2
3
4
5
6
7
8
9
10
11
// a 就是一个  Observable 实例, 并且a有一个唯一的全局id
const a = obserable({
name: 'nickname'
})

// dependencMap 的 key就是 上面的那个 a的唯一全局id
const dependencMap = {
a.objectID = [
...hanlder // 依赖这个 observable 值的函数 或者 组件
]
}

当一个被 observable 包装的属性值发生 set 行为的时候,就会触发 dependenceManager.trigger(obID); 从而触发遍历对应的监听函数列表,并且执行,这就是 observer 的基本原理, 我们一般用observer来包裹组件, 那么当组件依赖的数据有变更时, 组件就会执行render函数, 实现组件界面刷新

这个依赖的 map 如何做生成的呢?

observer里面有个的 autoRun 方法很关键

1
2
3
4
5
6
7
8
// 组件被渲染之前, 会标记一下, 这个组件正在执行依赖收集
dependenceManager.beginCollect(handler);

// 执行组件的render逻辑
handler();

// 结束组件依赖收集
dependenceManager.endCollect();

当一个组件处于依赖收集的状态, 在执行渲染的时候, 如果使用了某个 observable 值, 由于 observable 对象是被Proxy 包裹过的, 实际上会通过Proxy中的 get 的方法能取到最终的值, 这时候只需要把这个值 和 当前正在收集依赖的 handler 关联起来, 并存储在 dependencMap 中, 就完成依赖收集了.

MobX还些其他的特性, 例如 computed, 这里就不一一阐述, 我们能理解大致的设计理念即可 详情可以参考 MobX

Immer的作者跟Mobx的作者是同一个人, 可以说Mobx 的底层在Proxy的使用上, 理念跟 immer 是如出一辙的

Vue

在国内火的一塌糊涂的 VUE其标榜的也是响应式编程的理念

在实现响应式的方式上, 跟Mobx非常类似, 不过 Vue 使用 Object.defineProperty 把对象的属性全部转为 getter/setter, 进而实现 追踪依赖,并在属性被访问和修改时通知变化

Vue的官方文档已经写的非常详细了, 这里不重复阐述, 强烈建议点进去看一下 深入响应式原理