本文是 Vue3源码系列教程,从基础到进阶,附完整代码示例,收藏备用!
一、前言
Vue3 的响应式系统是其最核心的特性之一,相比 Vue2 的 Object.defineProperty,Vue3 使用 Proxy 实现了更强大、更灵活的响应式能力。本文将深入源码,带你彻底理解 reactive 和 ref 的实现原理。
学完本文,你将掌握:
- Proxy 与 Reflect 的基本用法
- reactive 的创建过程与依赖收集
- ref 的包装原理与 .value 访问
- effect 副作用函数的执行机制
- 手写一个极简版响应式系统
二、Vue3 响应式系统概览
2.1 核心 API
javascript
import { reactive, ref, computed, effect } from 'vue'
// reactive: 创建对象的响应式代理
const state = reactive({ count: 0 })
// ref: 创建值的响应式引用
const count = ref(0)
// computed: 创建计算属性
const double = computed(() => count.value * 2)
// effect: 注册副作用函数
effect(() => {
console.log('count changed:', count.value)
})
2.2 响应式系统架构
┌─────────────────────────────────────────┐
│ 用户代码 (Component) │
├─────────────────────────────────────────┤
│ reactive() │ ref() │ computed() │
├─────────────────────────────────────────┤
│ Proxy 代理对象 (Handlers) │
├─────────────────────────────────────────┤
│ 依赖收集 (track) / 触发更新 (trigger) │
├─────────────────────────────────────────┤
│ Effect 副作用函数队列 │
└─────────────────────────────────────────┘
三、Proxy 与 Reflect 基础
3.1 为什么用 Proxy 替代 Object.defineProperty
| 特性 | Object.defineProperty | Proxy |
|---|---|---|
| 拦截能力 | 仅 get/set | 13种操作 |
| 新增属性 | 无法监听 | 自动监听 |
| 数组索引 | 需特殊处理 | 原生支持 |
| 性能 | 递归遍历 | 懒代理 |
| 兼容性 | IE9+ | IE不支持 |
3.2 Proxy 基本用法
javascript
const target = { name: 'Vue', version: 3 }
const proxy = new Proxy(target, {
// 拦截读取操作
get(target, key, receiver) {
console.log('get:', key)
return Reflect.get(target, key, receiver)
},
// 拦截设置操作
set(target, key, value, receiver) {
console.log('set:', key, '=', value)
return Reflect.set(target, key, value, receiver)
},
// 拦截 in 操作符
has(target, key) {
console.log('has:', key)
return Reflect.has(target, key)
},
// 拦截 delete 操作
deleteProperty(target, key) {
console.log('delete:', key)
return Reflect.deleteProperty(target, key)
}
})
proxy.name // get: name → 'Vue'
proxy.version = 4 // set: version = 4 → true
'name' in proxy // has: name → true
delete proxy.version // delete: version → true
3.3 Reflect 的作用
Reflect 提供了一套与 Proxy 处理器对应的方法,确保 this 指向正确:
javascript
const obj = {
_value: 0,
get value() {
return this._value
}
}
const proxy = new Proxy(obj, {
get(target, key, receiver) {
// receiver 是 proxy 本身
// 使用 Reflect.get 确保 getter 中的 this 指向 proxy
return Reflect.get(target, key, receiver)
}
})
四、reactive 源码解析
4.1 创建 reactive 对象
javascript
// 简化版实现
const reactiveMap = new WeakMap() // 缓存已创建的代理
function reactive(target) {
// 1. 检查是否是对象
if (!isObject(target)) {
console.warn(`value cannot be made reactive: ${String(target)}`)
return target
}
// 2. 检查是否已有代理
if (reactiveMap.has(target)) {
return reactiveMap.get(target)
}
// 3. 创建 Proxy 代理
const proxy = new Proxy(target, mutableHandlers)
// 4. 缓存代理对象
reactiveMap.set(target, proxy)
return proxy
}
function isObject(val) {
return val !== null && typeof val === 'object'
}
4.2 处理器对象 mutableHandlers
javascript
const mutableHandlers = {
get(target, key, receiver) {
// 收集依赖
track(target, key)
const result = Reflect.get(target, key, receiver)
// 递归代理嵌套对象(懒代理)
if (isObject(result)) {
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
}
}
4.3 依赖收集 track 与触发 trigger
javascript
// 存储依赖关系的数据结构
const targetMap = new WeakMap()
let activeEffect = null // 当前正在执行的 effect
// 收集依赖
function track(target, key) {
if (!activeEffect) return
// 获取 target 对应的 depsMap
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 获取 key 对应的 dep (Set)
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
// 收集当前 effect
dep.add(activeEffect)
// 建立双向引用,用于清理
activeEffect.deps.push(dep)
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
// 执行所有相关的 effect
dep.forEach(effect => {
if (effect.scheduler) {
effect.scheduler() // 异步调度
} else {
effect.run() // 同步执行
}
})
}
}
五、ref 源码解析
5.1 ref 的实现原理
ref 用于将基本类型包装成响应式对象:
javascript
function ref(value) {
return createRef(value, false)
}
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow
this._value = __v_isShallow ? value : toReactive(value)
this._rawValue = value
}
get value() {
// 收集依赖
trackRefValue(this)
return this._value
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
// 触发更新
triggerRefValue(this)
}
}
}
function toReactive(value) {
return isObject(value) ? reactive(value) : value
}
function isRef(r) {
return !!(r && r.__v_isRef === true)
}
function hasChanged(value, oldValue) {
return !Object.is(value, oldValue)
}
5.2 ref 的依赖收集
javascript
const refMap = new WeakMap()
function trackRefValue(ref) {
if (activeEffect) {
trackEffects(refMap.get(ref) || (refMap.set(ref, (new Set())), refMap.get(ref)))
}
}
function triggerRefValue(ref) {
const dep = refMap.get(ref)
if (dep) {
triggerEffects(dep)
}
}
5.3 ref 与 reactive 的区别
javascript
// reactive: 代理整个对象
const state = reactive({ count: 0 })
state.count++ // 直接修改属性
// ref: 包装单个值
const count = ref(0)
count.value++ // 通过 .value 访问
// ref 可以包装对象(自动用 reactive 转换)
const user = ref({ name: 'Tom' })
user.value.name = 'Jerry' // 响应式更新
六、effect 副作用函数
6.1 effect 的基本实现
javascript
function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
if (!options.lazy) {
_effect.run() // 立即执行
}
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
class ReactiveEffect {
constructor(fn, scheduler) {
this.fn = fn
this.scheduler = scheduler
this.deps = [] // 收集的依赖集合
this.active = true
}
run() {
if (!this.active) {
return this.fn()
}
// 设置当前激活的 effect
activeEffect = this
try {
return this.fn()
} finally {
activeEffect = null
}
}
stop() {
if (this.active) {
cleanupEffect(this)
this.active = false
}
}
}
// 清理 effect 的所有依赖
function cleanupEffect(effect) {
effect.deps.forEach(dep => {
dep.delete(effect)
})
effect.deps.length = 0
}
6.2 effect 的使用示例
javascript
const count = ref(0)
// 创建 effect
const stop = effect(() => {
console.log('count is:', count.value)
})
// 输出: count is: 0
count.value++ // 输出: count is: 1
count.value++ // 输出: count is: 2
// 停止 effect
stop()
count.value++ // 不再输出
6.3 调度器 scheduler
javascript
const count = ref(0)
effect(() => {
console.log('count:', count.value)
}, {
// 自定义调度,使用微任务队列
scheduler: (fn) => {
Promise.resolve().then(fn)
}
})
count.value = 1
count.value = 2
count.value = 3
// 输出: count: 0
// 只输出一次: count: 3(合并了多次更新)
七、手写极简版响应式系统
javascript
// ==================== 完整实现 ====================
let activeEffect = null
const targetMap = new WeakMap()
// 收集依赖
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
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())
}
}
// 创建响应式对象
function reactive(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
}
})
}
// 创建 ref
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newValue) {
value = newValue
trigger(refObject, 'value')
}
}
return refObject
}
// 注册 effect
function effect(fn) {
activeEffect = fn
fn()
activeEffect = null
}
// ==================== 使用示例 ====================
const state = reactive({ count: 0 })
effect(() => {
console.log('state.count =', state.count)
})
state.count++ // 输出: state.count = 1
state.count++ // 输出: state.count = 2
const num = ref(10)
effect(() => {
console.log('num.value =', num.value)
})
num.value = 20 // 输出: num.value = 20
八、computed 计算属性
8.1 computed 的实现
javascript
function computed(getter) {
let value
let dirty = true // 标记是否需要重新计算
const effectFn = effect(getter, {
lazy: true,
scheduler: () => {
dirty = true // 依赖变化时标记为脏
}
})
return {
get value() {
if (dirty) {
value = effectFn()
dirty = false
}
return value
}
}
}
// 使用
const count = ref(0)
const double = computed(() => count.value * 2)
console.log(double.value) // 0
count.value = 5
console.log(double.value) // 10
九、常见问题与解决方案
❌ 问题1:解构 reactive 对象失去响应式
javascript
const state = reactive({ count: 0, name: 'Vue' })
// ❌ 错误:解构后失去响应式
const { count } = state
count++ // 不会触发更新
// ✅ 正确:使用 toRefs
import { toRefs } from 'vue'
const { count, name } = toRefs(state)
count.value++ // 响应式更新
❌ 问题2:数组索引和 length 修改不触发更新
javascript
const arr = reactive([1, 2, 3])
// ✅ Vue3 支持以下操作
arr[0] = 10 // 触发更新
arr.length = 0 // 触发更新
arr.push(4) // 触发更新
❌ 问题3:reactive 不能直接替换整个对象
javascript
let state = reactive({ count: 0 })
// ❌ 错误:替换后失去响应式连接
state = { count: 1 }
// ✅ 正确:修改原对象的属性
state.count = 1
// 或改用 ref
const stateRef = ref({ count: 0 })
stateRef.value = { count: 1 } // 正常响应
❌ 问题4:异步获取数据后赋值不更新
javascript
const state = reactive({ list: [] })
// ❌ 可能不触发更新(如果 list 一开始不存在)
async function fetchData() {
state.list = await fetch('/api/list')
}
// ✅ 确保属性已定义
const state = reactive({ list: [] }) // 初始化时定义
// 或使用 ref
const list = ref([])
async function fetchData() {
list.value = await fetch('/api/list')
}
十、总结
Vue3 响应式系统的核心设计:
- Proxy 代理:拦截对象操作,实现深度响应式
- 懒代理:只在访问属性时才递归创建代理
- 依赖收集:通过 track 在读取时收集 effect
- 触发更新:通过 trigger 在修改时执行 effect
- ref 包装:用对象包裹基本类型,通过 .value 访问
性能优化要点:
- 大数据列表使用
shallowRef或shallowReactive减少代理开销 - 避免不必要的嵌套响应式对象
- 合理使用
computed缓存计算结果
💬 觉得有用的话,点个赞+收藏,关注我,每周持续更新实战教程!
标签:vue3 | 源码 | 响应式 | reactive | ref | proxy