首页 / Vue3 入门教程 / 响应式原理深入

Vue3 入门教程

响应式原理深入

本教程共 40 篇 · 第 37 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

VueVue3 入门教程响应式原理Proxytracktriggereffect

37. 响应式原理深入

本节目标:透过 API 看本质,搞懂 Vue 响应式系统是怎么追踪依赖、触发更新的。

响应式到底是什么意思

“响应式”就是数据变化时自动更新相关联的东西。用一个简单的例子来说明:

A0 = 1
A1 = 2
A2 = A0 + A1  // 3

当你把 A0 改成 2,A2 自动变成 4。JavaScript 默认不这样:

let A0 = 1
let A1 = 2
let A2 = A0 + A1  // 3

A0 = 2
console.log(A2)  // 还是 3
Note

这个自动更新机制在第 1 章”Vue 简介”中首次介绍过。本节将深入讲解其底层实现原理。

怎么让 JS 实现这个效果?分三步:

  1. 追踪:谁读了 A0、A1?记录下来。
  2. 触发:A0 变了,通知所有读过它的代码。
  3. 执行:那些代码重新运行。

track 和 trigger

Vue 用 Proxy(或 getter/setter)拦截对象属性的读写:

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)    // 记录:有人读了
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)  // 通知:值变了
    }
  })
}

track 做了什么?检查当前有没有正在运行的”副作用”(effect),有就把它加到该属性的订阅列表里。

let activeEffect

function track(target, key) {
  if (activeEffect) {
    const effects = getSubscribersForProperty(target, key)
    effects.add(activeEffect)
  }
}

trigger 做了什么?找到该属性的所有订阅者,逐个调用。

function trigger(target, key) {
  const effects = getSubscribersForProperty(target, key)
  effects.forEach((effect) => effect())
}

订阅关系用全局 WeakMap 存储:WeakMap<target, Map<key, Set<effect>>>

Reactive Effect

把上面的串起来,就是 Vue 的响应式副作用:

function whenDepsChange(update) {
  const effect = () => {
    activeEffect = effect     // 标记自己为当前活动 effect
    update()                  // 执行时会触发 track
    activeEffect = null       // 清除标记
  }
  effect()                    // 首次执行,建立订阅关系
}

Vue 暴露了这个能力:watchEffect()

import { ref, watchEffect } from 'vue'

const A0 = ref(0)
const A1 = ref(1)
const A2 = ref()

watchEffect(() => {
  A2.value = A0.value + A1.value  // 读了 A0、A1,自动追踪
})

A0.value = 2  // A2 自动重新计算

computed 内部也是用 reactive effect 管理依赖。

Vue 组件的渲染本身就是一个 reactive effect——追踪模板里用到的所有响应式数据,数据变化时重新渲染。

ref vs reactive

ref 用 getter/setter 实现,reactive 用 Proxy 实现:

// ref 的简化实现
function ref(value) {
  return {
    get value() {
      track(this, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(this, 'value')
    }
  }
}

两者各有局限:

局限说明
解构丢失响应性把 reactive 的属性解构到局部变量,不再响应
引用不同reactive(obj) !== obj,Proxy 创建了新对象

响应式调试

响应式自动追踪很方便,但有时候你想搞清楚”到底追踪了什么”或”什么触发了更新”。

组件调试钩子

import { onRenderTracked, onRenderTriggered } from 'vue'

onRenderTracked((event) => {
  debugger  // 追踪到新的依赖时暂停
})

onRenderTriggered((event) => {
  debugger  // 某个依赖触发更新时暂停
})

事件对象包含这些信息:

字段说明
effect当前的 reactive effect
target触发更新的目标对象
type操作类型(get/set/add 等)
key变化的属性名
newValue / oldValue新旧值
Note

这些调试钩子只在开发模式生效,生产环境自动移除。

计算属性调试

const plusOne = computed(() => count.value + 1, {
  onTrack(e) {
    debugger  // count.value 被追踪为依赖时暂停
  },
  onTrigger(e) {
    debugger  // count.value 变化触发重新计算时暂停
  }
})

侦听器调试

watch(source, callback, {
  onTrack(e) { debugger },
  onTrigger(e) { debugger }
})

集成外部状态

Vue 的响应式是深度代理。但有些外部状态库(比如 Immer、XState)不需要深度代理,甚至深度代理会出问题。

通用方案:用 shallowRef 包一层。shallowRef 只追踪 .value 的变化,内部值保持原样。外部状态变化时,替换 .value 触发更新。

Immer 集成

import { produce } from 'immer'
import { shallowRef } from 'vue'

export function useImmer(baseState) {
  const state = shallowRef(baseState)
  const update = (updater) => {
    state.value = produce(state.value, updater)
  }
  return [state, update]
}

XState 状态机集成

import { createMachine, interpret } from 'xstate'
import { shallowRef } from 'vue'

export function useMachine(options) {
  const machine = createMachine(options)
  const state = shallowRef(machine.initialState)
  const service = interpret(machine)
    .onTransition((newState) => (state.value = newState))
    .start()
  const send = (event) => service.send(event)
  return [state, send]
}

与 Signals 的对比

近年来其他框架提出的 Signals(Solid、Angular、Preact)本质上跟 Vue ref 一样——值容器 + 依赖追踪 + 变化触发。

API 设计上的取舍:

风格访问值更新值特点
Vue ref.value.value =简单一致
Solid Signalcount()setCount(1)读写分离,更安全
Angular Signalcount()count.set(1)方法调用

Vue 也支持自定义 API 风格。用 shallowRef + triggerRef 就能模拟 Solid 的读写分离。

本节回顾

  • Vue 响应式基于 Proxy(reactive)和 getter/setter(ref)
  • track 追踪谁读了属性,trigger 在属性变化时通知订阅者
  • 响应式副作用(effect)自动追踪依赖,依赖变化时重新执行
  • 组件渲染本身就是 reactive effect
  • onRenderTracked/onRenderTriggered 用于调试
  • 外部状态用 shallowRef 集成
  • Vue ref 跟其他框架的 Signals 本质相同,API 风格有别