手写 Vue3 响应式系统:核心就一个数据结构
响应式是 Vue 的特色,如果你简历里写了 Vue 项目,那基本都会问响应式实现原理。
而且不只是 Vue,状态管理库 Mobx 也是基于响应式实现的。
那响应式是具体怎么实现的呢?
与其空谈原理,不如让我们来手写一个简易版吧。
响应式
首先,什么是响应式呢?
响应式就是被观察的数据变化的时候做一系列联动处理。
就像一个社会热点事件,当它有消息更新的时候,各方媒体都会跟进做相关报道。
这里社会热点事件就是被观察的目标。
那在前端框架里,这个被观察的目标是什么呢?
很明显,是状态。
状态一般是多个,会通过对象的方式来组织。所以,我们观察状态对象的每个 key 的变化,联动做一系列处理就可以了。
我们要维护这样的数据结构:
状态对象的每个 key 都有关联的一系列 effect 副作用函数,也就是变化的时候联动执行的逻辑,通过 Set 来组织。
每个 key 都是这样关联了一系列 effect 函数,那多个 key 就可以放到一个 Map 里维护。
这个 Map 是在对象存在的时候它就存在,对象销毁的时候它也要跟着销毁。(因为对象都没了自然也不需要维护每个 key 关联的 effect 了)
而 WeakMap 正好就有这样的特性,WeakMap 的 key 必须是一个对象,value 可以是任意数据,key 的对象销毁的时候,value 也会销毁。
所以,响应式的 Map 会用 WeakMap 来保存,key 为原对象。
这个数据结构就是响应式的核心数据结构了。
比如这样的状态对象:
const obj = {
a: 1,
b: 2
}
它的响应式数据结构就是这样的:
const depsMap = new Map();
const aDeps = new Set();
depsMap.set(a, aDeps);
const bDeps = new Set();
depsMap.set(b, bDeps);
const reactiveMap = new WeakMap()
reactiveMap.set(obj, depsMap);
创建出的数据结构就是图中的那个:
然后添加 deps 依赖,比如一个函数依赖了 a,那就要添加到 a 的 deps 集合里:
effect(() =