跳到主要内容

Vue 响应式原理

理解 Vue 的响应式系统对于深入掌握 Vue 至关重要。本章将详细介绍 Vue 3 响应式系统的工作原理。

响应式基础

什么是响应式?

响应式是指当数据变化时,依赖这些数据的视图会自动更新:

// 响应式行为
const data = reactive({ count: 0 })

// 当 data.count 变化时
data.count = 1 // 视图自动更新

Vue 2 vs Vue 3

特性Vue 2Vue 3
实现方式Object.definePropertyProxy
添加属性需要 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 响应式原理:

  1. 响应式概念:数据变化自动更新视图
  2. Proxy 代理:Vue 3 响应式的基础
  3. reactive 实现:对象响应式
  4. ref 实现:基本类型响应式
  5. 依赖追踪:track 和 trigger
  6. Computed 原理:缓存机制
  7. Watch 原理:监听数据变化
  8. 深度响应式:深层代理

练习

  1. 手写一个简单的 reactive 函数
  2. 分析 Computed 的缓存机制
  3. 理解 watchEffect 和 watch 的区别

准备好继续深入学习了吗?