跳到主要内容

依赖收集

Vue 2 的依赖收集

在 Vue 2 中,依赖收集是基于 Object.defineProperty 实现的。

Vue 2 中的每个组件实例有一个对应的 watcher 实例,它作为一个观察者,负责将数据变化转化为视图更新。当组件渲染时,它会访问所需的响应式数据,这些数据的 getter 函数被调用,watcher 就在这个时刻被收集为依赖。

换句话说,Vue 2 中收集的是与每个响应式数据相关联的 watcher 对象。

[初始化响应式数据]

[组件渲染,执行渲染函数]

[读取响应式数据,触发getter]

[依赖收集:将当前watcher添加到数据的Dep中]

[响应式数据变化,触发setter]

[派发更新:Dep实例通知所有watcher执行update]

[重新渲染组件]

每个响应式数据(例如,data 中的属性)通过 Object.defineProperty 被转换为 getter/setter 形式。当数据被读取(getter 被访问)时,当前的 watcher(表示当前正在渲染的组件)被添加到这个数据的依赖列表中;当数据被修改(setter 被调用)时,所有依赖于该数据的 watcher 会被通知,触发组件的重新渲染。

// 响应式系统的简化实现
class Dep {
constructor() {
this.subscribers = new Set();
}

depend() {
if (activeWatcher) {
this.subscribers.add(activeWatcher);
}
}

notify() {
this.subscribers.forEach(watcher => watcher.update());
}
}

// 模拟当前正在执行的 watcher
let activeWatcher = null;

function defineReactive(obj, key, val) {
const dep = new Dep();

Object.defineProperty(obj, key, {
get() {
dep.depend(); // 收集依赖
return val;
},
set(newVal) {
val = newVal;
dep.notify(); // 派发更新
}
});
}

Vue 3 的依赖收集

Vue 3 则采用了 ES6 的 Proxy 来实现响应式系统,这使得依赖收集和触发更新的过程更为直接和灵活。

在 Vue 3 中,effect 函数扮演着类似于 Vue 2 中 watcher 的角色,但它是基于更通用的**响应式效果(副作用)**概念实现的。

[创建响应式数据 (reactive/ref)]

[组件渲染/副作用执行 (effect)]

[读取响应式数据 -> 触发 Proxy 的 get]

[依赖收集 (Track) - 将副作用添加到依赖列表]

[修改响应式数据 -> 触发 Proxy 的 set]

[派发更新 (Trigger) - 查找并标记依赖的副作用]

[副作用重新执行 - 可能的再次依赖收集]

当使用 reactive 或 ref 创建响应式数据时,任何对这些数据的读取操作都会触发依赖收集:当前激活的 effect(如果有的话)会被添加到这些数据的依赖列表中。相应地,当响应式数据被修改时,所有收集的依赖(effect)会被重新执行,从而更新视图或执行其他副作用。

1. 响应式对象和引用

Vue 3 使用 reactive 和 ref 函数来创建响应式对象和响应式引用。这些函数内部通过 Proxy 来拦截对象的操作(如属性读取、属性设置等),使得数据变化可以被自动追踪。

  • reactive 用于创建响应式的对象或数组。
  • ref 用于创建一个响应式的值。

2. 依赖收集的工作流程

当渲染函数或计算属性被访问时,它们会读取响应式状态(通过 reactive 或 ref 创建的数据)。此时,Proxy 的 get 操作被触发,这是依赖收集发生的时刻:

访问响应式数据:执行组件的渲染函数或其他响应式状态(如计算属性),在这个过程中访问了响应式对象的属性。 触发 Proxy 的 get:读取操作触发 Proxy 的 get 拦截器。 依赖收集(Track):在 get 拦截器中,Vue 会检查是否有当前激活的副作用(activeEffect),如果有,将这个副作用(即依赖)添加到当前属性的依赖列表中。这是通过一个全局的 Dep 映射来管理的。

3. 更新派发

当响应式数据发生变化时(如属性被设置新值),Proxy 的 set 拦截器被触发,这启动了更新派发过程:

  • 修改响应式数据:执行写操作,比如 state.count = newCount。
  • 触发 Proxy 的 set:写操作触发 Proxy 的 set 拦截器。
  • 派发更新(Trigger):在 set 拦截器中,Vue 查找所有依赖于被修改属性的副作用,并重新执行它们,从而导致组件重新渲染或计算属性重新计算。

4. 副作用(Effect)

在 Vue 3 中,副作用是通过 effect 函数封装的。effect 函数接受一个副作用函数作为参数,并自动执行它。当副作用函数内部访问响应式数据时,这些数据的读取操作会触发依赖收集,将副作用函数注册为依赖。

import { reactive, effect } from 'vue';

const state = reactive({ count: 0 });

effect(() => {
console.log(state.count); // 读取操作,触发依赖收集
});

state.count++; // 写操作,触发更新派发

总结

  • Vue 2:依赖收集的是 watcher。每次数据被访问时,就会将当前的组件 watcher 收集到该数据的依赖列表中,数据变化时通知所有 watcher 更新视图。
  • Vue 3:依赖收集的是 effect。使用 Proxy 使得任何对响应式数据的访问都能自动收集当前激活的 effect 作为依赖,数据变化时重新执行这些 effect 以响应更新。