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

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

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

这篇不从源码堆概念,而是用"你在项目里一定会踩的坑"为主线,把 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 更省事但更容易过度依赖
相关推荐
Csvn1 天前
Monorepo 迁移血泪史:从 Multi-Repo 到 Turborepo,这 3 个坑我帮你踩完了
前端
星栈1 天前
Dioxus 多页面怎么做:`dioxus-router`、嵌套路由、`Outlet` 和页面组织,一篇给你讲顺
前端·rust·前端框架
用户987409238871 天前
用 Remotion + edge-tts 打造中文教学视频全自动流水线
前端
风骏时光牛马1 天前
Less前端工程化实战:变量混合器与项目样式分层落地
前端
假如让我当三天老蒯1 天前
Options API(选项式 API) 和 Composition API(组合式 API)
前端·vue.js·面试
SameX1 天前
iOS 独立开发实践:用 MapKit + 像素渲染实现 Citywalk 轨迹地图 App「雁过留痕」
前端
skyey1 天前
页面加载时,深色模式闪白的问题解决
前端
IT_陈寒1 天前
Java 并行流把我坑惨了,这6小时加班值了
前端·人工智能·后端
anOnion1 天前
构建无障碍组件之Menu Button pattern
前端·html·交互设计
用户47949283569151 天前
claude Fable用不了?把Gpt 5.5pro接到你的claude code里
前端·后端