Vue3 响应式系统:`ref`/`reactive`/`watchEffect` 的工作方式与最佳实践

你写 Vue3 时经常会遇到这些"似懂非懂"的问题:

  • 为什么 ref 需要 .value?
  • 为什么 reactive 解构后就"不响应"了?
  • watch、watchEffect、computed 到底怎么选?

这篇不从源码堆概念,而是用"你在项目里一定会踩的坑"为主线,把 Vue3 响应式讲清楚。


1. 响应式的目标是什么

目标只有一个:

  • 当某个状态发生变化时,能定位到"谁用到了它",并触发对应的更新。

对应两个关键动作:

  • 依赖收集:谁在读取这个状态?
  • 派发更新:这个状态变了,要通知谁重新执行?

Vue3 用 Proxy/Reflect + effect(副作用函数)完成这件事。


2. reactive:对象的响应式

reactive(obj) 返回一个 Proxy:

  • 读取属性(get)时:收集依赖
  • 修改属性(set)时:触发更新

适合:

  • 状态本身是一个对象/数组
  • 需要深层属性自然响应

2.1 常见坑:解构丢响应

js 复制代码
const state = reactive({ count: 0 })
const { count } = state
count++ // 这里不会触发视图更新

原因:解构得到的是一个普通值,不再经过 Proxy 的 get。

解决:

  • 用 toRefs(state) / toRef(state, 'count')
js 复制代码
import { reactive, toRefs } from 'vue'

const state = reactive({ count: 0 })
const { count } = toRefs(state)
count.value++

3. ref:单值/原始类型的响应式

ref(0) 返回形如 { value: 0 } 的对象,它本身也会被响应式处理。

为什么要有 .value:

  • 为了让原始类型也能用同一套"依赖收集/触发更新"的机制

适合:

  • 数字/字符串/布尔值等原始类型
  • 你希望"一个变量就是一个状态"

3.1 ref 包对象会怎样

js 复制代码
const user = ref({ name: 'a' })
user.value.name = 'b' // 也能响应

本质上:ref 内部会把对象也做成响应式(类似 reactive)。


4. computed:带缓存的派生状态

特点:

  • 懒执行:没人读就不算
  • 有缓存:依赖不变不重新算

适合:

  • 由多个状态推导出的展示字段
  • 避免把"计算逻辑"散落在模板里

5. watch vs watchEffect

5.1 watch

  • 你明确知道要监听谁(source)
  • 你需要 old/new
  • 你需要精细控制(immediate / deep / flush)
js 复制代码
watch(
  () => state.keyword,
  (newVal, oldVal) => {
    fetchList(newVal)
  },
  { immediate: true }
)

5.2 watchEffect

  • 你不想声明 source
  • 只要 effect 内读取到的响应式数据变化,就重新执行
js 复制代码
watchEffect(() => {
  fetchList(state.keyword)
})

注意:watchEffect 很方便,但也更容易"无意间读到多余依赖"导致频繁触发。


6. 实战建议(项目里最实用的选择策略)

  • 状态是对象/表单 :优先 reactive,配合 toRefs 暴露字段
  • 状态是单值 :优先 ref
  • 派生展示字段 :用 computed
  • 请求联动 :优先 watch(能控粒度、能拿 old/new、好排查)
  • 自动收集依赖的副作用 :再用 watchEffect

7. 常见坑与排查

  • 响应式失效 :优先检查"是否解构了 reactive"
  • watch 不触发 :检查 source 是否是 getter(watch(() => obj.x, ...))
  • 频繁触发 :检查 watchEffect 内是否读取了多余状态

8. 总结

  • Vue3 响应式核心是:依赖收集 + 派发更新
  • reactive 适合对象,但解构会丢响应,需要 toRefs
  • ref 让原始类型也能响应式
  • computed 做派生字段,带缓存
  • watch 更可控,watchEffect 更省事但更容易过度依赖
相关推荐
ym hyd 1113 小时前
试题库管理系统源码 Java+SpringBoot+Vue3 前后分离
java·vue.js·spring boot·毕设
迅猛龙办公室3 小时前
Python输出当前计算机的系统日期和时间
开发语言·前端·python
小狼154543 小时前
浏览器扩展脚本为什么有时候不生效:注入时机、iframe 和单页路由,多多开票助手
前端·chrome
じòぴé南冸じょうげん4 小时前
油猴脚本突然发现变成灰色了,无法使用?页面不生效?刷新页面没反应?
前端
Csvn5 小时前
组合式 API(Composition API)
前端
火柴就是我6 小时前
Android 打包报错 25.0.3
android·前端
BD_Marathon6 小时前
消息对象中字段的说明
java·前端·python
八荒启·交互动画6 小时前
Web特效025—用 Canvas 2D 做Web特效的定义与边界:这支画笔能做到哪一步
前端·webgl·网页特效·八荒启-交互动画·八荒启
Ai-_Man7 小时前
您您这可以把Dola的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。用AI导出鸭,答案是可以的
开发语言·前端·人工智能·小程序
excel7 小时前
Nuxt 中使用 useHead 优化 SEO 与 GEO
前端