前言
谈起 reactive
,大家都会想起 Vue3 的「响应式数据劫持」(或者依赖追踪)
通过 Proxy
创建响应式对象,在对象属性被访问时收集副作用(一个可执行函数),在对象属性被修改时依次执行副作用函数,从而达到数据更新响应通知。
数据劫持思路是通用的,也可用于 React
环境,比如 Formily
表单框架所提供的 @formily/reactive
和 @formily/reactive-react
,借助 Proxy 实现依赖追踪,从而达到表单控件的高效更新、按需渲染。
如果,我们也掌握了这一技能,在日常开发中的某一时刻,也可以借用它来优化组件渲染,来提高程序性能。
下面,我们从「使用」到「实现」,来理解 reactive
的核心原理,最后我们来看看如何接入到 React 组件实现按需渲染(数据更新,组件重渲染)。
一、observable & autorun
observable
,用于创建一个可观察的响应式对象,在这个对象的属性发生变化时,通知到依赖它的 autorun 中注册的tracker
回调;autorun
,接收一个 tracker 函数,会初始化时会执行一次,让observable
去收集它;当函数内部消费的observable
数据发生变化时,tracker 函数会重新执行。
1.使用:
// test.js
const { observable, autorun } = require('./reactive');
const obs = observable({name: '初始值'
});
autorun(() => {console.log(obs.name);
});
obs.name = '变更值';
autorun 接收的 tracker 函数在初始化时执行一次,因为访问了 observable
数据,在数据发生变化时,会再次执行 tracker。输出如下:
初始值
变更值
2.原理:
- 变量定义 1,
RawReactionsMap
是一个WeakMap
,存储每个对象属性收集过来的副作用函数(tracker);使用WeakMap
的好处是可以接受对象作为 key; - 变量定义 2,
currentReaction
记录了 autorun 中的 tracker 函数; - 创建 Proxy,核心是执行
createObservable
定义每个对象的get
和set
逻辑; - get 收集工作,对于上面示例,访问
obs.name
是发生在 autorun/tracker 函数中,会将 tracker 收集在RawReactionsMap
集合; - set 通知工作,在
RawReactionsMap
中查找并执行收集到的 autorun/tracker 函数。
observable
实现如下:
// reactive.js
// 1. 第一部分,定义变量
const RawReactionsMap = new WeakMap(); // 存储副作用
const RawProxy = new WeakMap(); // 记录对象是否被 Proxy 过
let currentReaction; // autorun 中的 tracker 函数
// 2、存储 tracker 函数
const addRawReactionsMap = (target, key, reaction) => {const reactionsMap = RawReactionsMap.get(target);if (reactionsMap) {const reactions = reactionsMap.get(key);if (reactions) {reactions.add(reaction);} else {reactionsMap.set(key, new Set([reaction]));}return reactionsMap;} e