Vue3响应式原理深度剖析

Vue3响应式原理深度剖析

Vue3响应式

Vue3 的响应式系统是其最核心的功能之一,本文将深入剖析其实现原理,帮助你更好地理解和使用 Vue3。

一、响应式基础

1.1 什么是响应式

响应式是指数据的变化能够自动触发视图的更新。Vue3 使用 Proxy API 实现响应式,相比 Vue2 的 Object.defineProperty 有显著改进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Vue2 的响应式实现
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`获取 ${key}: ${val}`);
return val;
},
set(newVal) {
console.log(`设置 ${key}: ${val} -> ${newVal}`);
val = newVal;
// 触发视图更新
}
});
}

// Vue3 的响应式实现
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
console.log(`获取 ${key}`);
// 依赖收集
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`设置 ${key}: ${value}`);
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
// 触发更新
trigger(target, key);
}
return result;
}
});
}

1.2 Proxy vs Object.defineProperty

特性 Vue2 (Object.defineProperty) Vue3 (Proxy)
数组监听 需要特殊处理 原生支持
动态属性 无法监听新增属性 可以监听
性能 初始化时递归遍历 懒初始化
Map/Set 不支持 支持
兼容性 IE9+ IE 不支持

二、核心实现原理

2.1 reactive 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 简化版 reactive 实现
const targetMap = new WeakMap();
let activeEffect = null;

function reactive(target) {
// 如果是对象,使用 Proxy 包装
if (typeof target !== 'object' || target === null) {
return target;
}

// 防止重复代理
if (targetMap.has(target)) {
return targetMap.get(target);
}

const proxy = new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);

// 依赖收集
track(target, key);

// 深度响应式
if (typeof result === 'object' && result !== null) {
return reactive(result);
}

return result;
},

set(target, key, value, receiver) {
const oldValue = target[key];

// 数组长度变化特殊处理
const hadKey = key in target;
const result = Reflect.set(target, key, value, receiver);

// 触发更新
if (!hadKey) {
trigger(target, 'add', key);
} else if (oldValue !== value) {
trigger(target, 'set', key);
}

return result;
},

deleteProperty(target, key) {
const hadKey = key in target;
const result = Reflect.deleteProperty(target, key);

if (hadKey) {
trigger(target, 'delete', key);
}

return result;
}
});

targetMap.set(target, proxy);
return proxy;
}

2.2 ref 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ref 用于基本类型和对象
function ref(value) {
return {
__v_isRef: true,
get value() {
track(this, 'value');
return value;
},
set value(newValue) {
if (value !== newValue) {
value = newValue;
trigger(this, 'value');
}
}
};
}

// 使用示例
const count = ref(0);
console.log(count.value); // 0
count.value = 1;

2.3 toRef 和 toRefs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// toRef: 将对象的某个属性转为 ref
function toRef(target, key) {
return {
__v_isRef: true,
get value() {
return target[key];
},
set value(newValue) {
target[key] = newValue;
}
};
}

// toRefs: 将对象的多个属性转为 refs
function toRefs(target) {
const result = {};
for (const key in target) {
result[key] = toRef(target, key);
}
return result;
}

// 使用示例
const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state);
console.log(count.value); // 0
count.value++;
console.log(state.count); // 1

三、依赖收集与触发

3.1 effect 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// effect 副作用函数
function effect(fn, options = {}) {
const effectFn = () => {
// 清理旧依赖
cleanup(effectFn);

// 设置当前活跃的 effect
activeEffect = effectFn;

// 执行函数,触发依赖收集
const result = fn();

// 重置 activeEffect
activeEffect = null;

return result;
};

// 存储依赖
effectFn.deps = [];
effectFn.options = options;

// 立即执行一次
if (!options.lazy) {
effectFn();
}

return effectFn;
}

function cleanup(effectFn) {
const deps = effectFn.deps;
if (deps) {
for (let i = 0; i < deps.length; i++) {
const dep = deps[i];
dep.delete(effectFn);
}
deps.length = 0;
}
}

3.2 track 依赖收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 依赖收集
function track(target, key) {
if (!activeEffect) return;

// 获取 target 的依赖映射
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}

// 获取 key 的依赖集合
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}

// 将当前 effect 添加到依赖集合
dep.add(activeEffect);

// 将依赖集合添加到 effect 的 deps 中
activeEffect.deps.push(dep);
}

3.3 trigger 触发更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 触发更新
function trigger(target, type, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;

const effects = new Set();

// 收集需要触发的 effects
if (key !== undefined) {
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effects.add(effect));
}
}

// 触发 effects
effects.forEach(effect => {
if (effect !== activeEffect) {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else {
effect();
}
}
});
}

四、computed 计算属性

4.1 computed 实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 计算属性实现
function computed(getterOrOptions) {
let getter;
let setter;

if (typeof getterOrOptions === 'function') {
getter = getterOrOptions;
setter = () => {
console.warn('Computed value is read-only');
};
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}

let dirty = true;
let value;
let computed;

const runner = effect(getter, {
lazy: true,
scheduler: () => {
dirty = true;
trigger(computed, 'value');
}
});

computed = {
__v_isRef: true,
get value() {
track(computed, 'value');

if (dirty) {
value = runner();
dirty = false;
}

return value;
},
set value(newValue) {
setter(newValue);
}
};

return computed;
}

// 使用示例
const count = ref(0);
const doubled = computed(() => count.value * 2);
console.log(doubled.value); // 0
count.value = 1;
console.log(doubled.value); // 2

五、watch 监听器

5.1 watch 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
function watch(source, cb, options = {}) {
let getter;

if (typeof source === 'function') {
getter = source;
} else if (reactive.isReactive(source)) {
getter = () => traverse(source);
} else {
getter = () => traverse({ [source]: source });
}

let oldValue;
let cleanup;

const onCleanup = (fn) => {
cleanup = fn;
};

const job = () => {
if (cleanup) {
cleanup();
}

const newValue = runner();
cb(newValue, oldValue, onCleanup);
oldValue = newValue;
};

const runner = effect(getter, {
lazy: true,
scheduler: () => {
if (options.flush === 'post') {
Promise.resolve().then(job);
} else {
job();
}
}
});

if (options.immediate) {
job();
} else {
oldValue = runner();
}
}

function traverse(value, seen = new Set()) {
if (typeof value !== 'object' || value === null || seen.has(value)) {
return value;
}

seen.add(value);

if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], seen);
}
} else {
for (const key in value) {
traverse(value[key], seen);
}
}

return value;
}

5.2 watch 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const state = reactive({ count: 0, name: 'Vue' });

// 监听单个属性
watch(
() => state.count,
(newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
}
);

// 监听多个属性
watch(
[() => state.count, () => state.name],
([newCount, newName], [oldCount, oldName]) => {
console.log(`count: ${oldCount} -> ${newCount}`);
console.log(`name: ${oldName} -> ${newName}`);
}
);

// 深度监听
watch(
state,
(newVal, oldVal) => {
console.log('state 发生变化');
},
{ deep: true }
);

// 立即执行
watch(
() => state.count,
(val) => console.log('count:', val),
{ immediate: true }
);

// watchEffect
watchEffect(() => {
console.log('count:', state.count);
});

六、最佳实践

6.1 合理选择 reactive 和 ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ✅ 对象使用 reactive
const user = reactive({
name: 'Vue',
age: 3,
posts: []
});

// ✅ 基本类型使用 ref
const count = ref(0);
const isLoading = ref(false);

// ✅ 解构时使用 toRefs
function useUser() {
const user = reactive({
name: 'Vue',
age: 3
});

return toRefs(user);
}

const { name, age } = useUser();

6.2 避免响应式丢失

1
2
3
4
5
6
7
8
9
// ❌ 错误:响应式丢失
const state = reactive({ count: 0 });
const { count } = state; // count 不再是响应式
count++; // 不会触发更新

// ✅ 正确:使用 toRefs
const state = reactive({ count: 0 });
const { count } = toRefs(state);
count.value++; // 会触发更新

6.3 computed 缓存优化

1
2
3
4
5
6
7
8
// computed 有缓存,只在依赖变化时重新计算
const count = ref(0);
const doubled = computed(() => count.value * 2);

console.log(doubled.value); // 计算
console.log(doubled.value); // 使用缓存
count.value++;
console.log(doubled.value); // 重新计算

总结

Vue3 的响应式系统基于 Proxy 实现,具有以下优势:

  1. 更强的能力:支持数组和动态属性的监听
  2. 更好的性能:懒初始化,只在需要时创建响应式
  3. 更简洁的 API:ref、reactive、computed、watch 等更直观
  4. 更灵活的扩展:可以自定义 effects 和调度器

理解响应式原理,能帮助我们更好地使用 Vue3,也能在遇到问题时快速定位和解决。


深入理解原理,掌握核心技术!