Vue3源码解析--双向绑定

双向绑定是Vue 3响应式系统的核心特性之一,它通过 数据劫持依赖追踪 实现了数据与视图的自动同步。

1. 双向绑定的基本概念

双向绑定是指:

  • 当数据发生变化时,视图自动更新。
  • 当用户操作视图时,数据自动更新。

在 Vue 中,双向绑定通常通过 v-model 指令实现。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<input v-model="message" />
<p>{{ message }}</p>
</template>

<script>
export default {
data() {
return {
message: 'Hello, Vue!',
};
},
};
</script>
  • v-model 是语法糖,它相当于 **:value=”message” @input=”message = $event.target.value”**。
  • 当用户在输入框中输入内容时,message 会自动更新;当 message 变化时,输入框的内容也会自动更新。

2. 响应式系统的核心

Vue 3 的响应式系统基于 Proxy依赖追踪 实现。以下是其核心概念:

2.1 Proxy

  • Vue 3 使用 Proxy 对象劫持数据的读写操作。
  • 当访问数据时,收集依赖(即哪些组件或计算属性依赖于该数据)。
  • 当修改数据时,触发依赖更新。

2.2 依赖追踪

  • 每个响应式数据都有一个依赖列表(deps),用于存储所有依赖于它的副作用(如组件的渲染函数)。
  • 当数据变化时,遍历依赖列表,执行副作用。

3. 源码解析

Vue 3 的响应式系统源码位于 packages/reactivity 目录下。以下是其核心实现逻辑的解析。

3.1 响应式数据的创建

Vue 3 通过 reactive 函数将普通对象转换为响应式对象:

1
2
3
export function reactive<T extends object>(target: T): T {
return createReactiveObject(target);
}
  • createReactiveObject 是创建响应式对象的核心函数。

3.1.1 createReactiveObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createReactiveObject(target: object) {
const proxy = new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 收集依赖
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发依赖更新
return result;
},
});
return proxy;
}
  • Proxyget 拦截器:在访问属性时调用 track,收集依赖。
  • Proxyset 拦截器:在修改属性时调用 trigger,触发依赖更新。

3.2 依赖收集(track)

track 函数用于收集依赖:

1
2
3
4
5
6
7
8
9
10
11
12
export function track(target: object, key: unknown) {
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); // 将当前副作用添加到依赖列表
}
  • targetMap:全局的 WeakMap,用于存储每个对象的依赖关系。
  • depsMap:每个对象的依赖关系表,键是属性名,值是一个 Set(存储副作用)。
  • activeEffect:当前正在执行的副作用(如组件的渲染函数)。

3.3 依赖触发(trigger)

trigger 函数用于触发依赖更新:

1
2
3
4
5
6
7
8
export function trigger(target: object, key: unknown) {
const depsMap = targetMap.get(target);
if (!depsMap) return; // 没有依赖,直接返回
const effects = depsMap.get(key);
if (effects) {
effects.forEach((effect) => effect()); // 执行所有副作用
}
}
  • targetMap 中获取对象的依赖关系表。
  • depsMap 中获取属性的依赖列表。
  • 遍历依赖列表,执行每个副作用。

3.4 副作用(effect)

副作用是指依赖于响应式数据的操作(如组件的渲染函数)。Vue 3 通过 effect 函数创建副作用:

1
2
3
4
5
6
7
8
export function effect(fn: () => void) {
const effectFn = () => {
activeEffect = effectFn; // 设置当前副作用
fn();
activeEffect = null; // 清除当前副作用
};
effectFn(); // 立即执行副作用
}
  • activeEffect:全局变量,用于存储当前正在执行的副作用。
  • 当副作用执行时,会访问响应式数据,从而触发 track,将副作用添加到依赖列表。

3.5 v-model 的实现

v-model 是双向绑定的语法糖,其实现逻辑如下:

1
2
3
4
5
6
export function withModel(props: any, emit: any) {
return (event: Event) => {
const value = (event.target as HTMLInputElement).value;
emit('update:modelValue', value); // 触发更新事件
};
}
  • 当用户输入内容时,触发 input 事件。
  • 通过 emit 触发 update:modelValue 事件,更新数据。

4. 总结

Vue 3 的双向绑定基于响应式系统实现,其核心逻辑包括:

  1. 响应式数据:通过 Proxy 劫持数据的读写操作。
  2. 依赖收集:在访问数据时,收集依赖(副作用)。
  3. 依赖触发:在修改数据时,触发依赖更新。
  4. 副作用:通过 effect 函数创建和管理副作用。

通过以上机制,Vue 3 实现了高效的双向绑定,使数据与视图能够自动同步。理解其源码实现,有助于我们更好地使用和优化 Vue 应用。