Vue 响应式原理
理解 Vue 的响应式系统对于深入掌握 Vue 至关重要。本章将详细介绍 Vue 3 响应式系统的工作原理。
响应式基础
什么是响应式?
响应式是指当数据变化时,依赖这些数据的视图会自动更新:
// 响应式行为
const data = reactive({ count: 0 })
// 当 data.count 变化时
data.count = 1 // 视图自动更新
Vue 2 vs Vue 3
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 实现方式 | Object.defineProperty | Proxy |
| 添加属性 | 需要 Vue.set | 直接支持 |
| 删除属性 | 需要 Vue.delete | 直接支持 |
| 数组索引 | 需要 Vue.set | 直接支持 |
| 性能 | 一般 | 显著提升 |
Vue 3 响应式原理
Proxy 代理
Proxy 是 ES6 提供的代理器,可以拦截对象的所有操作:
const original = { count: 0 }
// 创建代理
const proxy = new Proxy(original, {
get(target, key) {
console.log(`获取 ${key}`)
return target[key]
},
set(target, key, value) {
console.log(`设置 ${key} = ${value}`)
target[key] = value
return true
}
})
proxy.count // 输出: 获取 count
proxy.count = 1 // 输出: 设置 count = 1
reactive() 的实现
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
// 依赖收集
track(target, key)
return Reflect.get(target, key)
},
set(target, key, value) {
const result = Reflect.set(target, key, value)
// 触发更新
trigger(target, key)
return result
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
trigger(target, key)
return result
}
})
}
ref() 的实现
function ref(value) {
// 如果是对象,使用 reactive 包装
const wrapper = {
get value() {
track(wrapper, 'value')
return value
},
set value(newValue) {
value = newValue
trigger(wrapper, 'value')
}
}
return wrapper
}
依赖追踪
什么是依赖追踪?
当组件使用响应式数据时,Vue 会记录这个依赖关系,当数据变化时,通知依赖它的组件更新:
┌─────────────────────────────────────────────────────────┐
│ 依赖追踪流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 组件读取数据(get) │
│ │ │
│ ▼ │
│ 2. 记录依赖关系(track) │
│ │ │
│ ▼ │
│ 3. 数据变化(set) │
│ │ │
│ ▼ │
│ 4. 通知依赖更新(trigger) │
│ │
└─────────────────────────────────────────────────────────┘
依赖收集过程
let currentEffect = null
function track(target, key) {
if (currentEffect) {
// 将当前 effect 添加到依赖收集器
const depsMap = target._depsMap || (target._depsMap = new Map())
const effects = depsMap.get(key) || (depsMap.set(key, new Set()), depsMap.get(key))
effects.add(currentEffect)
}
}
function trigger(target, key) {
const depsMap = target._depsMap
if (depsMap) {
const effects = depsMap.get(key)
effects?.forEach(effect => effect())
}
}
Computed 原理
Computed 实现
function computed(getter) {
let value
let dirty = true // 标记是否需要重新计算
const effect = () => {
value = getter()
dirty = true
}
return {
get value() {
if (dirty) {
currentEffect = effect
value = getter()
currentEffect = null
dirty = false
}
return value
}
}
}
Computed 缓存机制
// computed 缓存示例
const count = ref(1)
const doubled = computed(() => {
console.log('计算中...')
return count.value * 2
})
console.log(doubled.value) // 输出: 计算中... 输出: 2
console.log(doubled.value) // 输出: 2(使用缓存,不重新计算)
console.log(doubled.value) // 输出: 2(继续使用缓存)
count.value = 2 // 修改依赖
console.log(doubled.value) // 输出: 计算中... 输出: 4(重新计算)
Watch 原理
watchEffect 实现
function watchEffect(effect) {
currentEffect = effect
effect()
currentEffect = null
}
watch 实现
function watch(source, callback) {
let getter
if (typeof source === 'function') {
getter = source
} else {
getter = () => traverse(source)
}
let oldValue
const job = () => {
const newValue = getter()
if (newValue !== oldValue) {
callback(newValue, oldValue)
oldValue = newValue
}
}
// 使用 effect 包装
const effect = () => {
oldValue = getter()
}
job()
}
深度响应式
深层代理
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
const value = Reflect.get(target, key)
// 如果是对象,递归创建深层代理
if (value !== null && typeof value === 'object') {
return reactive(value)
}
track(target, key)
return value
},
set(target, key, value) {
const oldValue = target[key]
const result = Reflect.set(target, key, value)
if (result && oldValue !== value) {
trigger(target, key)
}
return result
}
})
}
shallowRef
function shallowRef(value) {
return {
_value: value,
get value() {
track(this, 'value')
return this._value
},
set value(newValue) {
this._value = newValue
trigger(this, 'value')
}
}
}
性能优化
减少响应式开销
// 避免不必要的响应式
const plainObject = { /* 不会被响应式 */ }
const reactiveObject = reactive({ /* 会被响应式 */ })
// 大列表使用 shallowRef
const largeList = shallowRef([])
// 需要的字段才响应式
const { id, name } = storeToRefs(userStore)
合理使用 watchEffect
// 避免在 watchEffect 中修改被监听的值
watchEffect(() => {
// 不要这样做,会导致无限循环
count.value = count.value + 1
})
// 正确的做法
watch(count, (newValue) => {
if (newValue > 10) {
count.value = 0
}
})
常见问题
响应式丢失
// 解构会丢失响应式
const { name } = reactive({ name: '张三' })
name = '李四' // 不会触发更新
// 使用 toRefs 保持响应式
import { toRefs } from 'vue'
const { name } = toRefs(reactive({ name: '张三' }))
name.value = '李四' // 会触发更新
响应式边界
// setup 返回的对象会自动转为响应式
setup() {
return {
// 这个对象会被深度响应式化
user: { name: '张三' }
}
}
// 手动暴露的对象不会
setup() {
const user = reactive({ name: '张三' })
return { user } // 需要在模板中使用 .value
}
小结
本章我们深入学习了 Vue 响应式原理:
- 响应式概念:数据变化自动更新视图
- Proxy 代理:Vue 3 响应式的基础
- reactive 实现:对象响应式
- ref 实现:基本类型响应式
- 依赖追踪:track 和 trigger
- Computed 原理:缓存机制
- Watch 原理:监听数据变化
- 深度响应式:深层代理
练习
- 手写一个简单的 reactive 函数
- 分析 Computed 的缓存机制
- 理解 watchEffect 和 watch 的区别
准备好继续深入学习了吗?