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
| 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; } }); }
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
| const targetMap = new WeakMap(); let activeEffect = null;
function reactive(target) { 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
| 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); 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
| function toRef(target, key) { return { __v_isRef: true, get value() { return target[key]; }, set value(newValue) { target[key] = newValue; } }; }
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); count.value++; console.log(state.count);
|
三、依赖收集与触发
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
| function effect(fn, options = {}) { const effectFn = () => { cleanup(effectFn); activeEffect = effectFn; const result = fn(); 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; 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); }
|
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(); if (key !== undefined) { const dep = depsMap.get(key); if (dep) { dep.forEach(effect => effects.add(effect)); } } 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); count.value = 1; console.log(doubled.value);
|
五、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(() => { 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
| const user = reactive({ name: 'Vue', age: 3, posts: [] });
const count = ref(0); const isLoading = ref(false);
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++;
const state = reactive({ count: 0 }); const { count } = toRefs(state); count.value++;
|
6.3 computed 缓存优化
1 2 3 4 5 6 7 8
| 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 实现,具有以下优势:
- 更强的能力:支持数组和动态属性的监听
- 更好的性能:懒初始化,只在需要时创建响应式
- 更简洁的 API:ref、reactive、computed、watch 等更直观
- 更灵活的扩展:可以自定义 effects 和调度器
理解响应式原理,能帮助我们更好地使用 Vue3,也能在遇到问题时快速定位和解决。
深入理解原理,掌握核心技术!