Skip to content
第 3 章 前端 ⏱ 13 分钟阅读

第 3 章:响应式 ref / reactive ​

学习目标 ​

  • 区分 ref 与 reactive 的使用场景
  • 用 toRefs 解构不丢失响应性
  • 理解 shallowRef 和 readonly 的边界
  • 避开响应式丢失的 4 个坑

一、ref:基本类型首选 ​

vue
<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)              // 基本类型
const msg = ref('hello')

count.value++                     // 脚本里要 .value
console.log(count.value)          // 1
</script>

<template>
  <p>{{ count }}</p>              <!-- 模板里自动解包,不用 .value -->
</template>

二、reactive:对象类型 ​

vue
<script setup lang="ts">
import { reactive } from 'vue'

const user = reactive({
  name: 'Alice',
  age: 18,
  addr: { city: '上海' },
})

user.age = 19                     // ⚠️ reactive 不需要 .value
user.addr.city = '北京'           // 深层响应
</script>
维度ref(0)reactive({})
适用基本 / 对象都行仅对象
访问.value直接 obj.key
解构丢失响应性丢失响应性
替换.value = {} 可整体替换❌ 不能再赋值

⚠️ 坑 1:千万别给 reactive 整体赋值 user = { ... },会丢失响应。改用 Object.assign(user, { ... })。

三、解构保留响应性 ​

ts
import { toRefs, toRef } from 'vue'

const user = reactive({ name: 'A', age: 18 })

const { name, age } = toRefs(user)    // 解构后仍是 Ref
const age2 = toRef(user, 'age')       // 单独拎一个字段

setup 返回的对象传到模板时,Vue 会自动解包 ref,所以模板里直接 &#123;&#123; name &#125;&#125; 即可。

💡 toRefs() 创建的 ref 跟原 reactive 对象是双向绑定——改 name.value 等于改 user.name,反过来也一样:

ts
name.value = 'B'
console.log(name.value)  // 'B'
console.log(user.name)   // 'B'  ← user 跟着变了

user.name = 'C'
console.log(name.value)  // 'C'  ← name 也跟着变了

底层是 getter/setter,两边读写都走同一个数据源,不是"复制"。所以 toRefs 是"指针"不是"快照"。

四、shallowRef 只追踪 .value 变化 ​

ts
import { shallowRef } from 'vue'

const state = shallowRef({ count: 0 })
state.value.count++               // ❌ 不会触发更新
state.value = { count: 1 }        // ✅ 替换引用才会更新

使用场景:大对象、第三方实例、只需整体替换时。

五、readonly 防止误改 ​

ts
import { readonly } from 'vue'

const origin = reactive({ name: 'A' })
const copy = readonly(origin)     // 深只读

copy.name = 'B'                   // ⚠️ 开发环境会报警告,生产静默失败

六、ref 与 reactive 互转 ​

ts
import { ref, unref, isRef } from 'vue'

const r = ref(0)
unref(r)              // 0,等价于 isRef(r) ? r.value : r
isRef(r)              // true

七、实战:表单对象 ​

vue
<script setup lang="ts">
import { reactive, ref } from 'vue'

const form = reactive({
  username: '',
  password: '',
})
const loading = ref(false)

const submit = async () => {
  loading.value = true
  try {
    await api.login(form)         // 直接传 reactive 对象
  } finally {
    loading.value = false
  }
}
</script>

⚠️ 坑 2:reactive 不能直接 await reactive(...),也不要把它放进 Map / Set 当 key。 ⚠️ 坑 3:ref({}) 在模板里访问子字段会自动解包(&#123;&#123; obj.a &#125;&#125; 不用 .value),但对象整体仍要 .value。 ⚠️ 坑 4:Pinia store 里优先用 ref 不用 reactive,避免 SSR 序列化问题。

八、本章小结 ​

API何时用
ref基本类型、需要 .value 语义
reactive复杂对象,内部全用 obj.key
toRefs / toRef解构保留响应性
shallowRef大数据 / 整体替换
readonly防止组件 props 被改

动手练习 ​

  1. 计数器:用 ref 写一个 + / - / 重置按钮
  2. 表单:用 reactive 写登录表单 { username, password },提交时打印
  3. 解构:把表单字段用 toRefs 拆出来,绑定到 input 上,确认还能响应

下一章:第 4 章:computed / watch →

本站基于 VitePress 构建 · 由 StackHub 团队维护