Vue3 响应式基础总结

一、ref

用于创建响应式数据,可包裹任意类型(原始值、对象、数组、Map、Set 等)。

import { ref } from 'vue'

const name = ref('Bill')   // 原始值
const user = ref({ name: 'Bill', age: 18 })  // 对象
const arr  = ref([1, 2, 3])  // 数组
  • 脚本中必须通过 .value 访问
  • 模板中会自动解包,无需 .value
<template>
  <div>{{ name }}</div>  <!-- 自动解包,无需 .value -->
</template>

<script setup>
const name = ref('Bill')
name.value = 'Tom'  // 脚本中需要 .value
</script>

深层响应式

ref 默认对嵌套对象也做深层追踪:

const user = ref({ name: 'Bill', nested: { count: 1 } })
user.value.nested.count++  // ✅ 触发更新

浅层版本:shallowRef

只追踪 .value 本身的替换,内部属性修改不触发更新:

const state = shallowRef({ count: 1 })

state.value.count++       // ❌ 不触发更新
state.value = { count: 2 } // ✅ 触发更新

二、DOM 异步更新 与 nextTick

响应式数据修改后,DOM 不是同步更新的,直接读取 DOM 拿到的是旧值。

count.value = 2
console.log(container.innerText) // 仍然是旧值

使用 nextTick() 等待 DOM 更新后再操作(推荐 async/await 写法):

import { nextTick } from 'vue'

count.value = 2
await nextTick()
console.log(container.innerText) // ✅ 最新值

三、reactive

专门将对象类型转为响应式(底层基于 Proxy API):

import { reactive } from 'vue'

const state = reactive({
  count1: 0,
  nested: { count2: 0 }
})

state.count1++              // ✅ 触发更新
state.nested.count2 += 2   // ✅ 深层也触发更新

ref 内部对复杂类型也调用了 reactive,对原始值则用 Object.defineProperty。

浅层版本:shallowReactive

只对第一层属性做响应式追踪,嵌套属性的修改不触发更新:

const state = shallowReactive({ count1: 0, nested: { count2: 0 } })

state.count1++            // ✅ 触发更新
state.nested.count2++     // ❌ 不触发更新

四、reactive 的局限性

问题 说明
类型限制 只接受对象类型,不能传原始值(string / number / boolean)
替换整个对象 state = reactive({...}) 会丢失响应式追踪
解构 / 函数传参 解构或传参后拿到的是普通值,失去响应式
let state = reactive({ count: 0 })

// ❌ 替换整个对象,原响应式丢失
state = reactive({ count: 1 })

// ❌ 解构后 count 是普通值,修改不触发更新
let { count } = state
count++

// ❌ 传参同理
func(state.count)

最佳实践:优先使用 ref,它没有上述限制。


五、ref 解包细节

场景 是否自动解包
模板中的顶级 ref ✅ 自动解包
模板中的非顶级 ref(如 object.id) ❌ 需手动 .value
作为 reactive 对象的属性 ✅ 自动解包
作为 shallowReactive 对象的属性 ❌ 不解包
作为 reactive 数组 / 集合的元素 ❌ 不解包
// reactive 对象属性 —— 自动解包
const name = ref('Bill')
const state = reactive({ name })
console.log(state.name)       // ✅ 'Bill'(自动解包)

// shallowReactive 对象属性 —— 不解包
const state2 = shallowReactive({ name })
console.log(state2.name.value) // 需要 .value

// reactive 数组元素 —— 不解包
const books = reactive([ref('Vue 3 Guide')])
console.log(books[0].value)    // 需要 .value

模板中仅顶级 ref 自动解包

<template>
  <div>{{ count }}</div>          <!-- ✅ 顶级 ref,自动解包 -->
  <div>{{ object.id + 1 }}</div>  <!-- ❌ 非顶级,结果为 [object Object]1 -->
  <div>{{ object.id.value + 1 }}</div> <!-- ✅ 手动 .value -->
</template>

<script setup>
const count = ref(0)
const object = { id: ref(1) }  // 非顶级 ref
</script>

shallowReactive 中切断 ref 关联的陷阱

const name = ref('Bill')
const obj = shallowReactive({ name })

// ❌ 赋为普通字符串,切断了与原 ref 的关联
obj.name = 'John'

// 之后修改原 ref 不再触发 obj 的更新
name.value = 'Smith'  // obj.name 不变

正确做法:操作 ref 的 .value

obj.name.value = 'John'  // ✅ 保持关联
posted @ 2026-05-18 19:59  HuangBingQuan  阅读(68)  评论(0)    收藏  举报