Vue3 的响应式系统源码级依赖收集与触发更新流程解析
字数 1445 2025-11-15 03:50:46
Vue3 的响应式系统源码级依赖收集与触发更新流程解析
题目描述:
请详细解析 Vue3 的响应式系统在源码层面如何实现依赖收集(track)和触发更新(trigger),包括核心数据结构的交互逻辑、执行时机以及异步调度细节。
1. 响应式系统的核心数据结构
Vue3 的响应式系统基于 Proxy 和 Reflect 实现,核心依赖三个数据结构:
- TargetMap:类型为
WeakMap<Target, KeyToDepMap>,用于存储所有响应式对象与其依赖的映射。 - KeyToDepMap:类型为
Map<Key, Dep>,存储响应式对象的每个属性对应的依赖集合。 - Dep:类型为
Set<ReactiveEffect>,存储与属性相关的所有副作用函数(effect)。
// 源码简化结构
type TargetMap = WeakMap<object, KeyToDepMap>;
type KeyToDepMap = Map<string | symbol, Dep>;
type Dep = Set<ReactiveEffect>;
2. 依赖收集(track)流程
当访问响应式对象的属性时,Proxy 的 get 拦截器会触发 track 函数:
步骤分解:
-
检查当前活跃的 effect:
- 只有处于
activeEffect(当前正在执行的副作用函数)时才会收集依赖。 - 若
activeEffect为null(例如在非 effect 上下文中访问属性),则直接返回。
- 只有处于
-
查找或创建依赖映射:
- 从
targetMap中查找当前对象(target)对应的depsMap,若不存在则创建新的Map。 - 从
depsMap中查找当前属性(key)对应的dep(依赖集合),若不存在则创建新的Set。
- 从
-
建立双向关联:
- 将
activeEffect添加到dep中(表示属性依赖此 effect)。 - 同时将
dep添加到activeEffect.deps数组中(用于后续清理依赖)。
- 将
// 简化版 track 逻辑
function track(target: object, key: string | symbol) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
activeEffect.deps.push(dep); // 记录 effect 的所有依赖
}
3. 触发更新(trigger)流程
当修改响应式对象的属性时,Proxy 的 set 拦截器会触发 trigger 函数:
步骤分解:
-
查找依赖集合:
- 从
targetMap中获取target对应的depsMap,若不存在则直接返回(说明从未被依赖)。 - 从
depsMap中获取key对应的dep(依赖集合)。
- 从
-
优化重复 effect:
- 使用
new Set()合并dep与其他可能相关的依赖(如数组的length变更),避免重复执行同一个 effect。
- 使用
-
调度执行 effect:
- 遍历依赖集合,若 effect 配置了
scheduler(如组件更新器),则交给调度器处理(异步批量更新)。 - 否则直接同步执行
effect.run()。
- 遍历依赖集合,若 effect 配置了
// 简化版 trigger 逻辑
function trigger(target: object, key: string | symbol) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const effects = new Set<ReactiveEffect>();
const addEffects = (dep: Dep) => {
dep && dep.forEach(effect => effects.add(effect));
};
addEffects(depsMap.get(key));
// 处理数组长度变更等特殊情况
if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, k) => {
if (k === 'length' || Number(k) >= (value as number)) {
addEffects(dep);
}
});
}
effects.forEach(effect => {
if (effect.options.scheduler) {
effect.options.scheduler(effect); // 进入异步调度队列
} else {
effect.run(); // 直接执行
}
});
}
4. 异步调度与批量更新
Vue3 通过 scheduler 实现异步批量更新,避免重复渲染:
- 组件更新 effect 的
scheduler会将更新任务推入队列,并通过nextTick批量执行。 - 示例:多次修改响应式数据时,仅触发一次组件更新。
// 调度器逻辑(简化版)
const queue: ReactiveEffect[] = [];
let isFlushing = false;
function scheduler(effect: ReactiveEffect) {
if (!queue.includes(effect)) {
queue.push(effect);
}
if (!isFlushing) {
isFlushing = true;
Promise.resolve().then(() => {
while (queue.length) {
queue.shift()!.run(); // 批量执行更新
}
isFlushing = false;
});
}
}
5. 依赖清理机制
Effect 重新执行前会清理旧依赖,避免无效依赖残留:
function cleanup(effect: ReactiveEffect) {
effect.deps.forEach(dep => dep.delete(effect)); // 从所有依赖集合中移除
effect.deps.length = 0; // 清空依赖数组
}
总结:
Vue3 的响应式系统通过 track 建立属性与 effect 的关联,通过 trigger 按需触发更新,结合调度器实现异步批量优化。源码层级的细节包括依赖集合的嵌套存储、双向关联的维护以及异步任务队列的管理,共同保证了响应式数据的高效更新。