带你彻底掌握 Vue 3.0 的响应式系统( 二 )


依赖收集阶段最重要的目的 , 就是建立一份”依赖收集表“ , 也就是图示的”targetMap" 。targetMap 是一个 WeakMap , 其 key 值是~~当前的 Proxy 对象 state~~代理前的对象origin , 而 value 则是该对象所对应的 depsMap 。
depsMap 是一个 Map , key 值为触发 getter 时的属性值(此处为 count) , 而 value 则是触发过该属性值所对应的各个 effect 。
还是有点绕?那么我们再举个例子 。假设有个 Proxy 对象和 effect 如下:
const state = reactive({count: 0,age: 18})const effect1 = effect(() => {console.log('effect1: ' + state.count)})const effect2 = effect(() => {console.log('effect2: ' + state.age)})const effect3 = effect(() => {console.log('effect3: ' + state.count, state.age)})复制代码那么这里的 targetMap 应该为这个样子:

带你彻底掌握 Vue 3.0 的响应式系统

文章插图
 
 
这样 , { target -> key -> dep } 的对应关系就建立起来了 , 依赖收集也就完成了 。代码如下:
export function track (target, operationType, key) {const effect = effectStack[effectStack.length - 1]if (effect) {let depsMap = targetMap.get(target)if (depsMap === void 0) {targetMap.set(target, (depsMap = new Map()))}let dep = depsMap.get(key)if (dep === void 0) {depsMap.set(key, (dep = new Set()))}if (!dep.has(effect)) {dep.add(effect)}}}弄明白依赖收集表 targetMap 是非常重要的 , 因为这是整个响应式系统核心中的核心 。
 
响应阶段回顾上一章节的例子 , 我们得到了一个 { count: 0, age: 18 } 的 Proxy , 并构造了三个 effect 。在控制台上看看效果:
带你彻底掌握 Vue 3.0 的响应式系统

文章插图
 
 
效果符合预期 , 那么它是怎么实现的呢?首先来看看这个阶段的原理图:
 
带你彻底掌握 Vue 3.0 的响应式系统

文章插图
 
 
当修改对象的某个属性值的时候 , 会触发对应的 setter 。
setter 里面的 trigger() 函数会从依赖收集表里找到当前属性对应的各个 dep , 然后把它们推入到 effects 和 computedEffects(计算属性) 队列中 , 最后通过 scheduleRun() 挨个执行里面的 effect 。
由于已经建立了依赖收集表 , 所以要找到属性所对应的 dep 也就轻而易举了 , 可以看看具体的代码实现:
export function trigger (target, operationType, key) {// 取得对应的 depsMapconst depsMap = targetMap.get(target)if (depsMap === void 0) {return}// 取得对应的各个 depconst effects = new Set()if (key !== void 0) {const dep = depsMap.get(key)dep && dep.forEach(effect => {effects.add(effect)})}// 简化版 scheduleRun , 挨个执行 effecteffects.forEach(effect => {effect()})}复制代码
这里的代码没有处理诸如数组的 length 被修改的一些特殊情况
至此 , 响应式阶段完成 。
 
总结阅读源码的过程充满了挑战性 , 但同时也常常被 Vue 的一些实现思路给惊艳到 , 收获良多 。本文按照响应式系统的运行过程 , 划分了”初始化“ , ”依赖收集“和”响应式“三个阶段 , 分别阐述了各个阶段所做的事情 , 应该能够较好地帮助读者理解其核心思路 。




推荐阅读