前言
Vue3 的响应式系统是整个框架的核心,与 Vue2 基于 Object.defineProperty 的方案不同,Vue3 全面采用 Proxy 实现数据劫持。本文将带你深入理解 reactive、ref 和 effect 三大核心 API 的实现原理。
Vue2 响应式的局限性
先回顾一下 Vue2 的响应式实现方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function defineReactive(obj, key) { let val = obj[key] Object.defineProperty(obj, key, { get() { console.log(`读取了 ${key}`) return val }, set(newVal) { console.log(`设置了 ${key}`) val = newVal } }) }
|
这种方式存在几个明显的问题:
- 无法检测对象属性的添加和删除 — 必须使用
Vue.set / Vue.delete
- 无法直接监听数组索引和 length 变化 — 需要重写数组的 7 个方法(
push、pop、shift 等)
- 初始化时需要递归遍历所有属性 — 嵌套深对象性能开销大
Vue3 核心:Proxy 劫持
Proxy 可以拦截对象几乎所有的操作,弥补了 Object.defineProperty 的短板。
reactive 的实现
reactive 是 Vue3 响应式的基础 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 36 37 38
| const targetMap = new WeakMap() let activeEffect = null
function reactive(target) { return new Proxy(target, { get(target, key, receiver) { console.log(`读取了属性: ${String(key)}`) 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) { console.log(`设置了属性: ${String(key)}`) trigger(target, key) } return result }, deleteProperty(target, key) { const hadKey = Object.prototype.hasOwnProperty.call(target, key) const result = Reflect.deleteProperty(target, key) if (hadKey) { console.log(`删除了属性: ${String(key)}`) trigger(target, key) } return result } }) }
|
Vue3 使用 Reflect API 来确保 this 指向的正确性,这是与手写 Proxy 的一个重要区别。
依赖收集与触发:track & trigger
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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) }
function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return const dep = depsMap.get(key) if (dep) { dep.forEach(effect => effect()) } }
|
数据结构关系如下:
| 层级 |
存储结构 |
键 |
值 |
| 第一层 |
WeakMap |
target 对象 |
depsMap |
| 第二层 |
Map |
属性名 key |
dep (Set) |
| 第三层 |
Set |
— |
effect 副作用函数 |
effect:调度副作用
1 2 3 4 5 6 7 8 9
| function effect(fn) { const _effect = () => { activeEffect = _effect fn() activeEffect = null } _effect() return _effect }
|
effect 会立即执行一次传入的函数,在函数执行过程中读取到的响应式数据会通过 track 把当前 effect 收集为依赖。数据变化时 trigger 会重新执行这些 effect。
ref 的实现
ref 本质是对 reactive 的一层包装,用于处理基本类型值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function ref(value) { return { get value() { track(this, 'value') return value }, set value(newValue) { if (newValue !== value) { value = newValue trigger(this, 'value') } } } }
|
在 Vue3 模板中使用 ref 时,会自动解包 .value,在 setup 函数中则需要手动 .value 访问。
computed 的简化实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function computed(getter) { let cached let dirty = true
const _effect = effect(() => { cached = getter() dirty = false })
return { get value() { if (dirty) { cached = getter() dirty = false } return cached } } }
|
computed 的核心是缓存:依赖不变时直接返回缓存值,避免重复计算。
总结
Vue3 响应式系统相对于 Vue2 的关键改进:
- Proxy 替代 Object.defineProperty — 能检测属性增删、数组变化,无需额外 API
- 懒代理 — 只在访问嵌套对象时才递归代理,初始化性能更好
- 清晰的依赖收集模型 —
track / trigger 分离,WeakMap → Map → Set 三级结构一目了然
- Reflect 保证 this 正确 — 避免了 Proxy 中 this 指代混乱的问题
理解响应式原理对于日常开发中排查渲染问题、优化性能以及面试都至关重要。
内容由 AI 生成,仅供参考。本文发布于 码上学习,转载请注明出处。