Vue3 响应式系统完全解析
响应式系统概述
Vue3 的响应式系统是整个框架的核心,它基于 Proxy 实现,相比 Vue2 的 Object.defineProperty,具有更强大的拦截能力和更好的性能表现。
什么是响应式?
响应式的核心思想是:当数据发生变化时,依赖这些数据的地方(如视图、计算属性、侦听器)能够自动更新。
// 期望的效果
const state = reactive({ count: 0 })
// 当 count 变化时,视图自动更新
state.count++ // 自动触发组件重新渲染
Vue3 响应式系统的三大核心
1️⃣ Proxy 代理层(基础)
-
使用
Proxy拦截对象的读取(get)和修改(set)操作 -
实现惰性深层代理:嵌套对象只在访问时才创建 Proxy,优化性能
2️⃣ 依赖收集层(订阅-发布)
-
track(订阅):在访问属性时,记录"哪个 effect 依赖了哪个属性"
-
trigger(发布):在修改属性时,触发所有依赖该属性的 effect 执行
3️⃣ 应用层(effect/computed/watch)
-
effect:副作用函数,是响应式系统的执行单元
-
computed:基于 effect 实现,具有缓存 + dirty 机制
-
watch:基于 effect 实现,用于监听数据变化并执行回调
-
组件渲染:组件的渲染函数本身就是一个 effect
核心数据结构
// 全局依赖映射表
const targetMap = new WeakMap<object, Map<any, Set<ReactiveEffect>>>()
/**
* 结构示意:
* targetMap: WeakMap {
* obj1: Map {
* 'count': Set { effect1, effect2 },
* 'name': Set { effect3 }
* },
* obj2: Map {
* 'age': Set { effect4 }
* }
* }
*/
解释:
-
WeakMap:键是原始对象,值是该对象的依赖 Map(弱引用,对象销毁时自动清理)
-
Map:键是对象的属性名,值是依赖该属性的 effect 集合
-
Set:存储所有依赖该属性的 effect,用 Set 自动去重
响应式系统的执行流程
┌─────────────────────────────────────────────────────────────┐
│ 创建响应式对象 │
│ reactive(obj) / ref(val) │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Proxy 代理(拦截 get/set) │
│ • 访问属性 → get 拦截 → track 收集依赖 │
│ • 修改属性 → set 拦截 → trigger 触发更新 │
│ • 嵌套对象 → 惰性创建 Proxy(按需代理) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ track 订阅 │ │ trigger 发布 │
│ 收集依赖关系 │ │ 触发副作用 │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 记录到 targetMap│ │ 从 targetMap │
│ 建立属性-effect │ │ 获取 effect │
│ 的映射关系 │ │ 并执行 │
└──────────────┘ └──────┬───────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 组件渲染 effect│ │ computed │ │ watch │
│ diff 更新 DOM │ │ dirty = true │ │ 执行回调 │
└──────────────┘ └──────────────┘ └──────────────┘
核心机制:Proxy 代理
reactive 的实现原理
reactive 函数接收一个普通对象,返回一个 Proxy 代理对象,拦截对象的各种操作。
function reactive<T extends object>(target: T): T {
// 如果已经是 Proxy,直接返回
if (target.__v_isReactive) {
return target
}
return new Proxy(target, {
// 读取属性时触发
get(target, key, receiver) {
// 特殊标记
if (key === '__v_isReactive') return true
const result = Reflect.get(target, key, receiver)
// 依赖收集(订阅)
track(target, key)
// 🔥 惰性深层代理:嵌套对象在访问时才创建 Proxy
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
},
// 删除属性时触发
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
trigger(target, key)
return result
},
// has、ownKeys 等其他拦截...
})
}
惰性深层代理(Lazy Deep Proxy)
这是 Vue3 响应式系统的重要优化,相比于初始化时递归代理所有嵌套对象,Vue3 采用按需代理策略。
传统方式(Vue2 / 初始化全递归)
// ❌ 性能问题:初始化时递归处理所有嵌套属性
function reactiveOld(obj) {
Object.keys(obj).forEach(key => {
if (isObject(obj[key])) {
obj[key] = reactiveOld(obj[key]) // 递归处理
}
Object.defineProperty(obj, key, {
get() { /*...*/ },
set() { /*...*/ }
})
})
return obj
}
const state = reactiveOld({
user: {
profile: {
address: {
city: 'Beijing' // 初始化时就全部处理,即使永远不访问
}
}
}
})
Vue3 方式(惰性代理)
// ✅ 优化:只在访问嵌套属性时才创建 Proxy
const state = reactive({
user: {
profile: {
address: {
city: 'Beijing'
}
}
}
})
// 1. 初始化:只对 state 本身创建 Proxy
console.log(state) // Proxy { user: { profile: {...} } }
// 2. 访问 state.user 时:触发 get 拦截,才对 user 创建 Proxy
console.log(state.user) // 此时 user 才变成 Proxy
// 3. 访问 state.user.profile 时:才对 profile 创建 Proxy
console.log(state.user.profile) // 此时 profile 才变成 Proxy
优势:
-
⚡ 性能优化:嵌套层级深但未访问的对象,不会创建 Proxy
-
💾 内存节省:减少不必要的 Proxy 对象创建
-
🎯 按需处理:只对真正访问的路径创建响应式
实际案例分析
const state = reactive({
a: { b: { c: 1 } },
x: { y: { z: 2 } }
})
// 场景 1:只访问 a.b.c
effect(() => {
console.log(state.a.b.c)
})
// ✅ 只有 state、state.a、state.a.b 被创建为 Proxy
// ❌ state.x、state.x.y 不会被代理(未访问)
// 场景 2:稍后访问 x.y.z
effect(() => {
console.log(state.x.y.z)
})
// ✅ 此时 state.x、state.x.y 才被创建为 Proxy
Proxy vs Object.defineProperty
| 特性 | Proxy(Vue3) | Object.defineProperty(Vue2) |
|---|---|---|
| 新增/删除属性 | ✅ 自动响应 | ❌ 需要 $set/$delete |
| 数组索引 | ✅ 直接支持 | ❌ 需要重写数组方法 |
| 嵌套对象 | ✅ 惰性代理 | ❌ 初始化全递归 |
| 拦截能力 | ✅ 13 种操作 | ❌ 仅 get/set |
| 性能 | ✅ 更好(惰性) | ❌ 初始化慢 |
| 兼容性 | ⚠️ IE11 不支持 | ✅ 全兼容 |
ref 的特殊处理
ref 用于基本类型的响应式,本质是一个包装对象:
function ref<T>(value: T) {
return {
_value: value,
get value() {
track(this, 'value') // 收集依赖
return this._value
},
set value(newVal) {
if (newVal !== this._value) {
this._value = newVal
trigger(this, 'value') // 触发更新
}
}
}
}
// 使用
const count = ref(0)
effect(() => {
console.log(count.value) // 通过 .value 访问
})
count.value++ // 触发更新
.value 为什么需要 ?
-
JavaScript 无法拦截基本类型(number、string)的读写
-
必须包装成对象,通过访问对象属性来触发 get/set
依赖收集:track 订阅
track 的核心作用
当访问响应式对象的属性时,track 函数负责记录当前正在执行的 effect 与该属性之间的依赖关系。
// 全局变量:当前正在执行的 effect
let activeEffect: ReactiveEffect | undefined
// 依赖收集函数
function track(target: object, key: string | symbol) {
// 如果没有 effect 在执行,不需要收集依赖
if (!activeEffect) return
// 1. 获取该对象的依赖 Map
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 2. 获取该属性的依赖 Set
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// 3. 将当前 effect 添加到依赖集合中
dep.add(activeEffect)
// 4. 反向记录:effect 也需要记录自己依赖了哪些属性(用于清理)
activeEffect.deps.push(dep)
}
完整的依赖收集流程
const state = reactive({ count: 0, name: 'Vue' })
effect(() => {
console.log(state.count) // 访问 count
})
/**
* 执行流程:
*
* 1. effect 函数执行时:
* activeEffect = currentEffect
*
* 2. 访问 state.count 时:
* 触发 Proxy 的 get 拦截
* → 调用 track(state, 'count')
*
* 3. track 内部建立映射:
* targetMap: WeakMap {
* state: Map {
* 'count': Set { currentEffect }
* }
* }
*
* 4. effect 执行完毕:
* activeEffect = undefined
*/
依赖收集的时机
const state = reactive({
count: 0,
name: 'Vue',
nested: { age: 18 }
})
effect(() => {
if (state.count > 0) {
console.log(state.name) // ✅ 收集 name 的依赖
}
console.log(state.nested.age) // ✅ 收集 nested、nested.age 的依赖
})
// 依赖关系:
// state.count → effect
// state.name → effect(仅当 count > 0)
// state.nested → effect
// state.nested.age → effect
避免重复收集
同一个 effect 对同一个属性只需要收集一次:
effect(() => {
console.log(state.count)
console.log(state.count) // 同一 effect 多次访问同一属性
console.log(state.count)
})
// ✅ Set 自动去重,effect 只会被收集一次
// targetMap.get(state).get('count') === Set { effect }(只有一个)
依赖清理机制
每次 effect 重新执行前,需要清理旧的依赖关系,避免内存泄漏:
const state = reactive({ ok: true, a: 1, b: 2 })
effect(() => {
// 根据条件收集不同的依赖
if (state.ok) {
console.log(state.a) // 收集 a
} else {
console.log(state.b) // 收集 b
}
})
/**
* 首次执行:state.ok = true
* → 收集依赖:ok, a
*
* 修改 state.ok = false,effect 重新执行:
* → 清理旧依赖:ok, a
* → 收集新依赖:ok, b
*
* 为什么要清理?
* 如果不清理,state.a 仍会保留对 effect 的引用
* 修改 state.a 会错误地触发 effect(但此时 effect 不再依赖 a)
*/
派发更新:trigger 发布
trigger 的核心作用
当修改响应式对象的属性时,trigger 函数负责找到所有依赖该属性的 effect 并执行它们。
function trigger(target: object, key: string | symbol) {
// 1. 获取该对象的依赖 Map
const depsMap = targetMap.get(target)
if (!depsMap) return // 没有被依赖,直接返回
// 2. 获取该属性的依赖 Set
const dep = depsMap.get(key)
if (!dep) return // 该属性没有被依赖
// 3. 执行所有依赖该属性的 effect
const effects = new Set(dep) // 复制一份,避免执行过程中修改原 Set
effects.forEach(effect => {
// 避免无限循环:如果当前正在执行的 effect - 执行
#### 3️⃣ **应用层**(effect/computed/watch)
- **effect**:副作用函数,是响应式系统的执行单元
- **computed**:基于 effect 实现,具有缓存 + dirty 机制
- **watch**:基于 effect 实现,用于监听数据变化并执行回调
- **组件渲染**:组件的渲染函数本身就是一个 effect
### 核心数据结构
```typescript
// 全局依赖映射表
const targetMap = new WeakMap<object, Map<any, Set<ReactiveEffect>>>()
/**
* 结构示意:
* targetMap: WeakMap {
* obj1: Map {
* 'count': Set { effect1, effect2 },
* 'name': Set { effect3 }
* },
* obj2: Map {
* 'age': Set { effect4 }
* }
* }
*/
解释:
-
WeakMap:键是原始对象,值是该对象的依赖 Map(弱引用,对象销毁时自动清理)
-
Map:键是对象的属性名,值是依赖该属性的 effect 集合
-
Set:存储所有依赖该属性的 effect,用 Set 自动去重
响应式系统的执行流程
┌─────────────────────────────────────────────────────────────┐
│ 创建响应式对象 │
│ reactive(obj) / ref(val) │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Proxy 代理(拦截 get/set) │
│ • 访问属性 → get 拦截 → track 收集依赖 │
│ • 修改属性 → set 拦截 → trigger 触发更新 │
│ • 嵌套对象 → 惰性创建 Proxy(按需代理) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ track 订阅 │ │ trigger 发布 │
│ 收集依赖关系 │ │ 触发副作用 │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 记录到 targetMap│ │ 从 targetMap │
│ 建立属性-effect │ │ 获取 effect │
│ 的映射关系 │ │ 并执行 │
└──────────────┘ └──────┬───────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 组件渲染 effect│ │ computed │ │ watch │
│ diff 更新 DOM │ │ dirty = true │ │ 执行回调 │
└──────────────┘ └──────────────┘ └──────────────┘
effect 副作用系统
effect 是什么?
effect 是 Vue3 响应式系统的执行单元,所有的响应式更新都基于 effect 实现。
class ReactiveEffect {
active = true
deps: Set<ReactiveEffect>[] = [] // 该 effect 依赖的所有属性集合
constructor(
public fn: () => void, // 副作用函数
public scheduler?: (effect: ReactiveEffect) => void // 调度器
) {}
run() {
if (!this.active) {
return this.fn() // 如果 effect 已停止,直接执行不收集依赖
}
try {
activeEffect = this // 设置当前活动 effect
cleanupEffect(this) // 清理旧依赖
return this.fn() // 执行副作用函数(会触发依赖收集)
} finally {
activeEffect = undefined // 清空当前 effect
}
}
stop() {
if (this.active) {
cleanupEffect(this)
this.active = false
}
}
}
function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
_effect.run() // 立即执行一次
// 返回 runner 函数,可以手动触发
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
effect 的执行流程
const state = reactive({ count: 0 })
const runner = effect(() => {
console.log('count:', state.count)
})
/**
* 执行流程:
*
* 1. 创建 ReactiveEffect 实例
* 2. 立即执行 effect.run()
* - activeEffect = effect
* - cleanupEffect(effect) 清理旧依赖
* - 执行 fn() → 访问 state.count
* - 触发 track(state, 'count')
* - 建立依赖关系
* - activeEffect = undefined
* 3. 返回 runner 函数
*
* 修改 state.count = 1:
* 1. 触发 trigger(state, 'count')
* 2. 找到依赖的 effect
* 3. 执行 effect.run()
* 4. 重新收集依赖
*/
停止 effect
const state = reactive({ count: 0 })
const runner = effect(() => {
console.log(state.count)
})
// 停止 effect
runner.effect.stop()
// 修改 count,不会触发 effect
state.count++ // 不会输出
effect 的依赖清理
function cleanupEffect(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect) // 从每个依赖集合中移除自己
}
deps.length = 0 // 清空依赖数组
}
}
为什么要清理?
const state = reactive({ ok: true, a: 1, b: 2 })
effect(() => {
if (state.ok) {
console.log(state.a)
} else {
console.log(state.b)
}
})
// 首次:收集 ok, a
// state.ok = false 后重新执行:应该收集 ok, b
// 如果不清理,state.a 仍会错误地触发 effect
computed 计算属性
computed 的核心机制
computed 基于 effect 实现,但具有缓存 + dirty 机制,只有在依赖变化且被访问时才重新计算。
class ComputedRefImpl {
private _value: any
private _dirty = true // 脏值标记
public readonly effect: ReactiveEffect
constructor(getter: () => any) {
// 创建 effect,但使用调度器而非立即执行
this.effect = new ReactiveEffect(getter, () => {
// 依赖变化时,只标记 dirty,不立即计算
if (!this._dirty) {
this._dirty = true
trigger(this, 'value') // 通知依赖 computed 的 effect
}
})
}
get value() {
// 收集依赖该 computed 的 effect
track(this, 'value')
// 只有 dirty 为 true 时才重新计算
if (this._dirty) {
this._dirty = false
this._value = this.effect.run() // 执行计算函数
}
return this._value // 返回缓存值
}
}
function computed(getter: () => any) {
return new ComputedRefImpl(getter)
}
computed 的完整流程
const state = reactive({ count: 0 })
const double = computed(() => {
console.log('计算中...')
return state.count * 2
})
console.log(double.value) // 输出:计算中... 0
console.log(double.value) // 直接返回缓存 0(不输出"计算中...")
state.count = 1 // 只标记 dirty = true,不计算
console.log(double.value) // 输出:计算中... 2(重新计算)
console.log(double.value) // 直接返回缓存 2
执行流程详解:
1. 创建 computed:
- 创建 ReactiveEffect,传入 getter 和 scheduler
- dirty = true
2. 首次访问 double.value:
- track(double, 'value') 收集依赖
- dirty === true,执行 effect.run()
→ 执行 getter() → 访问 state.count
→ track(state, 'count') 建立 effect 与 count 的依赖
- 缓存结果 _value = 0
- dirty = false
3. 再次访问 double.value:
- dirty === false,直接返回缓存 0(不重新计算)
4. 修改 state.count = 1:
- trigger(state, 'count')
- 触发 computed 的 effect.scheduler()
- scheduler 中:dirty = true(只标记,不计算)
- trigger(double, 'value') 通知依赖 double 的 effect
5. 再次访问 double.value:
- dirty === true,重新计算
- _value = 2, dirty = false
computed 的懒计算特性
const state = reactive({ a: 1, b: 2 })
const sum = computed(() => {
console.log('计算 sum')
return state.a + state.b
})
state.a = 10 // 不会输出"计算 sum"(未访问 computed)
state.b = 20 // 不会输出"计算 sum"
console.log(sum.value) // 输出:计算 sum 30(访问时才计算)
核心优势:
-
✅ 只在依赖变化 + 被访问时才重新计算
-
✅ 避免不必要的计算开销
computed 的缓存机制
const state = reactive({ count: 0 })
const expensive = computed(() => {
console.log('执行复杂计算...')
let sum = 0
for (let i = 0; i < 1000000; i++) {
sum += state.count
}
return sum
})
// 在一次渲染中多次访问
effect(() => {
console.log(expensive.value)
console.log(expensive.value)
console.log(expensive.value)
})
/**
* 只会输出一次"执行复杂计算..."
* 因为:
* 1. 首次访问:dirty = true,执行计算,缓存结果
* 2. 后续访问:dirty = false,直接返回缓存
*/
computed vs 普通函数
const state = reactive({ count: 0 })
// ❌ 普通函数:每次调用都重新计算
function getDouble() {
console.log('计算...')
return state.count * 2
}
// ✅ computed:有缓存,只在依赖变化时重新计算
const double = computed(() => {
console.log('计算...')
return state.count * 2
})
effect(() => {
console.log(getDouble()) // 每次都输出"计算..."
console.log(getDouble())
console.log(getDouble())
console.log(double.value) // 只输出一次"计算..."
console.log(double.value)
console.log(double.value)
})
computed 的链式依赖
const state = reactive({ count: 0 })
const double = computed(() => state.count * 2)
const quadruple = computed(() => double.value * 2)
effect(() => {
console.log('quadruple:', quadruple.value)
})
/**
* 依赖链:
* state.count → double → quadruple → effect
*
* state.count = 1 时:
* 1. trigger(state, 'count')
* 2. double.scheduler() → double.dirty = true, trigger(double, 'value')
* 3. quadruple.scheduler() → quadruple.dirty = true, trigger(quadruple, 'value')
* 4. effect 重新执行
* 5. 访问 quadruple.value → 重新计算 → 访问 double.value → 重新计算
*/
watch 侦听器
watch 是 Vue3 中用于监听数据变化并执行自定义回调的 API,本质是对 effect 的封装,但与普通 effect、computed 相比,有明确的“监听目标”和“回调执行时机”,更适合处理“数据变化后做额外操作”的场景(如接口请求、日志打印、状态同步等)。
watch 核心特点:可监听单个/多个响应式数据,支持惰性执行/立即执行,可获取新旧值,支持深度监听,还能手动停止监听。
watch 的底层实现原理
watch 的本质是创建一个带有“监听逻辑”的 effect,核心流程分为 3 步:
-
创建
getter函数:用于读取监听目标的值(单个值/多个值),触发依赖收集; -
创建 effect:将 getter 作为 effect 的副作用函数,同时传入自定义调度器(scheduler);
-
依赖变化时:trigger 触发 effect 的调度器,在调度器中执行用户传入的回调,并传入新旧值。
简化版源码实现(贴合 Vue3 核心逻辑):
function watch(source, callback, options = {}) {
// 1. 处理监听目标:将 source 转为 getter 函数(统一格式)
let getter
if (typeof source === 'function') {
// 监听函数返回值(如 watch(() => state.count, ...))
getter = source
} else if (isReactive(source)) {
// 监听整个响应式对象(深度监听)
getter = () => traverse(source)
} else if (isRef(source)) {
// 监听 ref
getter = () => source.value
} else {
// 非响应式数据,不收集依赖
getter = () => {}
}
// 2. 存储旧值(首次执行时获取)
let oldValue
// 3. 创建调度器:依赖变化时执行回调
const scheduler = () => {
const newValue = runner() // 重新执行 getter,获取新值
// 执行用户回调,传入新旧值
callback(newValue, oldValue, () => {
// 手动触发监听(forceUpdate)
runner()
})
// 更新旧值,为下一次变化做准备
oldValue = newValue
}
// 4. 创建 effect:getter 作为副作用,scheduler 控制回调执行
const runner = effect(getter, {
lazy: true, // 懒执行:首次不触发 effect(默认不执行回调)
scheduler // 依赖变化时,执行调度器(而非直接执行 effect)
})
// 5. 处理立即执行(immediate: true)
if (options.immediate) {
scheduler() // 立即执行回调,此时 oldValue 为 undefined
} else {
oldValue = runner() // 首次执行 getter,获取旧值
}
// 6. 返回停止监听的函数
return () => {
runner.effect.stop() // 停止 effect,清理依赖
}
}
// 辅助函数:遍历响应式对象,触发所有属性的 get,实现深度监听
function traverse(value, seen = new Set()) {
if (!isObject(value) || seen.has(value)) return value
seen.add(value)
for (const key in value) {
traverse(value[key], seen)
}
return value
}
watch 的核心使用场景与示例
Vue3 中 watch 有 3 种常见使用方式,覆盖不同监听场景,面试常考:
1. 监听单个响应式数据(最常用)
监听 reactive 对象的单个属性、ref 值,适合精准监听某个数据的变化。
const state = reactive({ count: 0, name: 'Vue' })
const countRef = ref(0)
// 监听 reactive 单个属性(需用函数返回)
watch(
() => state.count,
(newVal, oldVal) => {
console.log('count 变化:', oldVal, '→', newVal)
}
)
// 监听 ref 值(直接传入)
watch(
countRef,
(newVal, oldVal) => {
console.log('countRef 变化:', oldVal, '→', newVal)
}
)
// 触发变化
state.count++ // 输出:count 变化:0 → 1
countRef.value++ // 输出:countRef 变化:0 → 1
2. 监听多个响应式数据
传入数组,监听多个数据,任意一个变化都会触发回调,回调中接收的新旧值也是数组。
const state = reactive({ count: 0, name: 'Vue' })
watch(
[() => state.count, () => state.name],
([newCount, newName], [oldCount, oldName]) => {
console.log('count 变化:', oldCount, '→', newCount)
console.log('name 变化:', oldName, '→', newName)
}
)
// 触发变化
state.count++ // 输出 count 变化,name 无变化
state.name = 'Vue3' // 输出 name 变化,count 无变化
3. 监听整个 reactive 对象(深度监听)
直接传入 reactive 对象,默认会深度监听(无需手动设置 deep: true),任意嵌套属性变化都会触发回调。
const state = reactive({
user: {
profile: {
age: 18
}
}
})
// 监听整个 reactive 对象
watch(
state,
(newVal, oldVal) => {
console.log('state 变化:', oldVal.user.profile.age, '→', newVal.user.profile.age)
}
)
// 触发深层变化
state.user.profile.age = 20 // 输出:state 变化:18 → 20
watch 的关键配置项(面试高频)
watch 支持 3 个核心配置项,决定回调的执行时机和行为,是面试重点:
1. immediate:立即执行回调
默认 false(惰性执行):只有数据变化时才执行回调;设置为 true,watch 初始化时会立即执行一次回调(此时 oldValue 为 undefined)。
watch(
() => state.count,
(newVal, oldVal) => {
console.log('回调执行:', oldVal, '→', newVal)
},
{ immediate: true } // 立即执行
)
// 初始化时输出:回调执行:undefined → 0(无需修改 count)
2. deep:深度监听
默认情况:
-
监听 reactive 对象:默认
deep: true,自动深度监听; -
监听单个属性(如
() => state.user.profile.age):默认deep: false,只监听当前属性,不监听嵌套属性; -
手动设置
deep: true,可实现对单个属性的深度监听(极少用)。
const state = reactive({
user: {
profile: {
age: 18
}
}
})
// 监听单个深层属性,需手动设置 deep: true
watch(
() => state.user,
(newVal) => {
console.log('age 变化:', newVal.profile.age)
},
{ deep: true }
)
state.user.profile.age = 20 // 输出:age 变化:20
3. flush:回调执行时机
控制回调在“DOM 更新前”“DOM 更新后”还是“同步执行”,默认flush: 'pre'(DOM 更新前执行),解决“回调中操作 DOM 但 DOM 未更新”的问题。
-
flush: 'pre'(默认):DOM 更新前执行回调; -
flush: 'post':DOM 更新后执行回调(适合操作 DOM); -
flush: 'sync':数据变化后立即同步执行回调(性能较差,慎用)。
watch(
() => state.count,
() => {
console.log('DOM 状态:', document.getElementById('count').innerText)
},
{ flush: 'post' } // DOM 更新后执行
)
state.count++ // 此时 DOM 已更新,能获取到最新的 innerText
watch vs computed(面试必问)
很多面试会问“watch 和 computed 有什么区别”,核心差异在于“用途”和“执行机制”,用表格清晰区分:
| 维度 | watch | computed |
|---|---|---|
| 核心用途 | 监听数据变化,执行副作用(如接口请求、DOM 操作) | 依赖数据计算,返回一个新值(用于模板渲染、逻辑复用) |
| 执行机制 | 数据变化时触发回调(可配置立即执行) | 懒计算 + 缓存,依赖变化且被访问时才重新计算 |
| 返回值 | 无返回值,靠回调执行逻辑 | 有返回值,可直接用于模板或逻辑 |
| 深度监听 | 需手动配置(监听单个属性时),默认支持 reactive 深度监听 | 自动深度监听依赖,无需配置 |
更多推荐



所有评论(0)