Vue3响应式原理深度解析
前言
Vue3相比Vue2最大的变化之一就是响应式系统的重写,从Object.defineProperty到Proxy,带来了更强大的功能和更好的性能。本文将深入剖析Vue3响应式系统的实现原理,帮助你理解Vue3的核心机制。
一、Vue2 vs Vue3响应式对比
1.1 Vue2的局限性
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
| const data = { count: 0 };
Object.defineProperty(data, 'count', { get() { console.log('get count'); return this._count; }, set(val) { console.log('set count'); this._count = val; } });
data.newProp = 'new';
const arr = [1, 2, 3]; arr[0] = 4; arr.length = 0;
|
1.2 Vue3的改进
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
| const data = { count: 0 };
const proxy = new Proxy(data, { get(target, key) { console.log('get', key); return target[key]; }, set(target, key, value) { console.log('set', key, value); target[key] = value; return true; } });
proxy.newProp = 'new';
const arr = reactive([1, 2, 3]); arr[0] = 4; arr.length = 0;
|
二、Proxy基础
2.1 Proxy核心API
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
| const proxy = new Proxy(target, handler);
const handler = { get(target, key, receiver) { console.log(`getting ${key}`); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log(`setting ${key} = ${value}`); return Reflect.set(target, key, value, receiver); }, has(target, key) { console.log(`checking ${key}`); return key in target; }, deleteProperty(target, key) { console.log(`deleting ${key}`); return delete target[key]; }, ownKeys(target) { console.log('ownKeys'); return Reflect.ownKeys(target); } };
|
2.2 Reflect的作用
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
|
const obj = {};
try { Object.defineProperty(obj, 'prop', { value: 1 }); console.log('success'); } catch (e) { console.log('fail'); }
if (Reflect.defineProperty(obj, 'prop', { value: 1 })) { console.log('success'); } else { console.log('fail'); }
const proxy = new Proxy(obj, { get(target, key, receiver) { return Reflect.get(target, key, receiver); } });
|
三、响应式核心实现
3.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
| const targetMap = new WeakMap();
function reactive(target) { if (typeof target !== 'object' || target === null) { return target; } const handler = { get(target, key, receiver) { track(target, key); const result = Reflect.get(target, key, receiver); if (typeof result === 'object' && result !== null) { return reactive(result); } return result; }, set(target, key, value, receiver) { const oldValue = target[key]; const result = Reflect.set(target, key, value, receiver); if (oldValue !== value) { trigger(target, key); } return result; }, deleteProperty(target, key) { const hadKey = hasOwn(target, key); const result = Reflect.deleteProperty(target, key); if (hadKey && result) { trigger(target, key); } return result; } }; return new Proxy(target, handler); }
function hasOwn(target, key) { return Object.prototype.hasOwnProperty.call(target, key); }
|
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 23 24 25 26 27 28 29
| let activeEffect = null; const effectStack = [];
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); }
const state = reactive({ count: 0 });
|
3.3 触发更新(trigger)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (!dep) return; const effects = new Set(dep); effects.forEach(effect => { if (effect !== activeEffect) { effect.run(); } }); }
const state = reactive({ count: 0 });
|
3.4 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 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
| class ReactiveEffect { constructor(fn, scheduler = null) { this.fn = fn; this.scheduler = scheduler; this.deps = []; this.active = true; } run() { if (!this.active) { return this.fn(); } try { activeEffect = this; effectStack.push(activeEffect); this.cleanup(); return this.fn(); } finally { effectStack.pop(); activeEffect = effectStack[effectStack.length - 1]; } } cleanup() { for (const dep of this.deps) { dep.delete(this); } this.deps.length = 0; } stop() { if (this.active) { this.cleanup(); this.active = false; } } }
function effect(fn, options = {}) { const _effect = new ReactiveEffect(fn, options.scheduler); _effect.run(); const runner = _effect.run.bind(_effect); runner.effect = _effect; return runner; }
const state = reactive({ count: 0 });
effect(() => { console.log(state.count); });
state.count++;
|
四、ref实现
4.1 基础ref
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
| class RefImpl { constructor(value) { this._value = convert(value); this.__v_isRef = true; this.dep = new Set(); } get value() { if (activeEffect) { this.dep.add(activeEffect); activeEffect.deps.push(this.dep); } return this._value; } set value(newValue) { if (newValue !== this._value) { this._value = convert(newValue); this.dep.forEach(effect => { if (effect !== activeEffect) { effect.run(); } }); } } }
function convert(value) { return typeof value === 'object' && value !== null ? reactive(value) : value; }
function ref(value) { return new RefImpl(value); }
const count = ref(0);
effect(() => { console.log(count.value); });
count.value++;
|
4.2 shallowRef
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 shallowRef(value) { const ref = { _value: value, __v_isRef: true, dep: new Set(), get value() { if (activeEffect) { this.dep.add(activeEffect); activeEffect.deps.push(this.dep); } return this._value; }, set value(newValue) { if (newValue !== this._value) { this._value = newValue; this.dep.forEach(effect => effect.run()); } } }; return ref; }
|
4.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 29 30 31 32 33 34 35
| function toRef(object, key) { return { __v_isRef: true, get value() { return object[key]; }, set value(newValue) { object[key] = newValue; } }; }
function toRefs(object) { const ret = {}; for (const key in object) { ret[key] = toRef(object, key); } return ret; }
const state = reactive({ name: 'John', age: 25 });
const { name, age } = toRefs(state);
name.value = 'Jane'; console.log(state.name);
|
五、computed实现
5.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
| class ComputedRefImpl { constructor(getter) { this._dirty = true; this._value = undefined; this.dep = new Set(); this.effect = new ReactiveEffect(getter, () => { if (!this._dirty) { this._dirty = true; this.dep.forEach(effect => effect.run()); } }); } get value() { if (activeEffect) { this.dep.add(activeEffect); activeEffect.deps.push(this.dep); } if (this._dirty) { this._value = this.effect.run(); this._dirty = false; } return this._value; } }
function computed(getter) { return new ComputedRefImpl(getter); }
const state = reactive({ count: 0 });
const double = computed(() => { console.log('computing...'); return state.count * 2; });
console.log(double.value); console.log(double.value);
state.count++; console.log(double.value);
|
六、watch实现
6.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 66 67 68 69
| function watch(source, cb, options = {}) { let getter; if (typeof source === 'function') { getter = source; } else if (source.__v_isRef) { getter = () => source.value; } else { getter = () => traverse(source); } let oldValue; const job = () => { const newValue = effectFn.run(); cb(newValue, oldValue); oldValue = newValue; }; const effectFn = new ReactiveEffect(getter, () => { if (options.flush === 'post') { Promise.resolve().then(job); } else { job(); } }); if (options.immediate) { job(); } else { oldValue = effectFn.run(); } return () => { effectFn.stop(); }; }
function traverse(value, seen = new Set()) { if (typeof value !== 'object' || value === null || seen.has(value)) { return value; } seen.add(value); for (const key in value) { traverse(value[key], seen); } return value; }
const state = reactive({ count: 0 });
watch( () => state.count, (newValue, oldValue) => { console.log(`count changed: ${oldValue} -> ${newValue}`); }, { immediate: true } );
state.count++;
|
七、响应式工具函数
7.1 isReactive和isRef
1 2 3 4 5 6 7 8 9 10 11
| function isReactive(value) { return !!(value && value.__v_isReactive); }
function isRef(value) { return !!(value && value.__v_isRef); }
function isProxy(value) { return isReactive(value) || isReadonly(value); }
|
7.2 unref和toValue
1 2 3 4 5 6 7 8 9 10
| function unref(ref) { return isRef(ref) ? ref.value : ref; }
function toValue(source) { return typeof source === 'function' ? source() : unref(source); }
|
7.3 proxyRefs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function proxyRefs(objectWithRefs) { return new Proxy(objectWithRefs, { get(target, key, receiver) { const value = Reflect.get(target, key, receiver); return isRef(value) ? value.value : value; }, set(target, key, newValue, receiver) { const oldValue = target[key]; if (isRef(oldValue) && !isRef(newValue)) { oldValue.value = newValue; return true; } return Reflect.set(target, key, newValue, receiver); } }); }
|
八、性能优化
8.1 shallowReactive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function shallowReactive(target) { return 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; } }); }
|
8.2 readonly
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
| function readonly(target) { return new Proxy(target, { get(target, key, receiver) { const result = Reflect.get(target, key, receiver); if (typeof result === 'object' && result !== null) { return readonly(result); } return result; }, set() { console.warn('Set operation failed: target is readonly'); return true; }, deleteProperty() { console.warn('Delete operation failed: target is readonly'); return true; } }); }
|
九、实战应用
9.1 自定义响应式状态
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
| import { reactive, computed, watch } from 'vue';
class Store { constructor(state) { this.state = reactive(state); this.getters = {}; this.mutations = {}; this.actions = {}; } registerGetter(name, fn) { this.getters[name] = computed(() => fn(this.state)); } commit(type, payload) { const mutation = this.mutations[type]; if (mutation) { mutation(this.state, payload); } } dispatch(type, payload) { const action = this.actions[type]; if (action) { return action(this, payload); } } }
const store = new Store({ count: 0 });
store.registerGetter('doubleCount', (state) => state.count * 2);
store.mutations.increment = (state) => { state.count++; };
watch( () => store.state.count, (val) => console.log('count:', val) );
|
9.2 响应式表单
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
| import { reactive, computed } from 'vue';
function useForm(initialValues) { const form = reactive({ values: { ...initialValues }, errors: {}, touched: {} }); const isValid = computed(() => { return Object.keys(form.errors).length === 0; }); function setFieldValue(field, value) { form.values[field] = value; } function setFieldTouched(field) { form.touched[field] = true; } function validate(rules) { form.errors = {}; for (const [field, fieldRules] of Object.entries(rules)) { const value = form.values[field]; for (const rule of fieldRules) { const error = rule(value); if (error) { form.errors[field] = error; break; } } } } return { form, isValid, setFieldValue, setFieldTouched, validate }; }
|
十、总结
Vue3响应式系统的核心:
关键概念
- Proxy: 代理对象,拦截操作
- track: 依赖收集
- trigger: 触发更新
- effect: 副作用函数
- ref/reactive: 创建响应式数据
优势
- ✅ 更完善的拦截能力
- ✅ 更好的性能(惰性代理)
- ✅ 更好的TypeScript支持
- ✅ 更灵活的组合式API
理解Vue3响应式原理,让你更好地驾驭Vue3!
参考资料