Skip to content

概念卡片:Vue3 响应式原理

一句话机制:Vue3 用 Proxy 代理对象实现响应式——ref/reactive 把数据包成响应式引用,模板或 watch 读取时「收集依赖」,数据变化时 Proxy 的 set 陷阱「触发更新」。响应式体系除了定义数据的 ref/reactive,还有**派生数据的 computed(缓存)监听变化的 watch(副作用)**两类配套。

四个数据 API 的分工

API作用访问/修改方式
ref(x)基本类型 → 响应式引用.value(模板自动解包)
reactive(obj)对象/数组 → 响应式代理直接 .属性
toRef(obj,'k')抽单属性、保持响应式.value
toRefs(obj)抽全部属性、保持响应式各属性 .value

关键代码示例

ref(基本类型,靠 .value 读写):

js
import { ref } from 'vue'
const message = ref('Hello World')
setTimeout(() => { message.value = '王源' }, 2000)  // 2 秒后视图自动更新

reactive + toRefs(对象,解构不丢响应式):

js
import { reactive, toRefs } from 'vue'
const dog = reactive({ id: 1, name: '哈士奇', age: 2 })
const { name, age } = toRefs(dog)   // 解构后仍是响应式
setTimeout(() => { name.value = '泰迪' }, 3000)

注意:toRef/toRefs 的前提是源对象本身已是响应式,否则抽出来的引用不响应。

computed(缓存派生值)与 watch(监听副作用)

js
import { ref, computed, watch } from 'vue'
const message = ref('Hello World')

// computed:依赖变化才重算,结果缓存
const reversedMessage = computed(() => message.value.split('').reverse().join(''))

// watch:数据变化后执行副作用(发请求、操作 DOM…)
watch(message, (newVal, oldVal) => {
  console.log('变了:', oldVal, '->', newVal)
})

watch 的三个参数与两个关键选项:

js
watch(来源, (newVal, oldVal) => {}, { deep, immediate })
选项作用默认
deep深度监听对象内部属性变化(对象默认监听不到内部属性)false
immediate初次加载立即执行一次回调false

watch 的来源可以是:ref、reactive 对象、getter 函数 () => user.address.city,或它们的数组。

完整对比

ref vs reactive:

refreactive
适用基本类型(也能包对象)仅对象/数组
读写.value直接 .属性
解构单值无此问题解构丢响应式,需 toRefs

computed vs watch:

computedwatch
用途派生一个新值(有返回值)监听变化执行副作用
缓存有(依赖不变不重算)
典型场景反转、过滤、合计请求、防抖、DOM 操作

不变量(必须成立的约束)

  • ref<script setup> 模板里自动解包,普通 <script> 里必须 .value
  • 解构 reactive 会丢响应式——必须 toRefs 解构。
  • computed 只有依赖的数据变化才重算,否则读缓存。
  • watch 监听对象内部属性默认监听不到,要设 deep: true
  • Proxy 能拦截新增/删除属性数组索引——Vue2 defineProperty 做不到,这是 Vue3 响应式的根本升级。

踩坑案例

  • 现象const { name } = reactive({name:'哈士奇'}) 解构后改 name 视图不更新。 原因:解构拿到的是普通值快照,已切断 Proxy 代理。解决const { name } = toRefs(dog)
  • 现象watch(obj, cb) 改了 obj.inner 却无回调。 原因:watch 默认浅监听,对象内部属性变化监听不到。解决watch(obj, cb, { deep: true })

常见误解

  • 以为 ref 只能存基本类型 → 也能存对象,内部自动转 reactive
  • 以为 computed 每次访问都重算 → 有缓存,依赖不变就返回缓存值。
  • 忘了 .valuesetup 内、watch 回调里直接给 ref 赋值,改了不生效。

关联

最近更新