Appearance
Vue 3 的响应式系统有多种工具函数,每个有特定的用途。本文详细介绍。
为什么需要 ref?
在 JavaScript 中,普通变量没有"通知机制":
js
let count = 0
count++ // 变了,但没人知道
Vue 需要在数据变化时自动更新 UI。普通变量做不到这一点,所以 Vue 提供了 ref —— 给变量包一层"响应式外壳":
js
const count = ref(0)
count.value++ // ✅ 变了,Vue 知道,UI 自动更新
ref 的本质:一个带有 .value 属性的响应式对象。ref(0) 返回的不是 0,而是 { value: 0 } 的代理对象。
可以把 ref 想象成快递追踪号——包裹还是那个包裹(值不变),但有了追踪号(ref),你就能实时知道它的状态变化。
ref — 基础类型首选
ref 创建一个响应式引用,适用于基本类型(数字、字符串、布尔值)。
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
const name = ref('张三')
function increment() {
count.value++ // ⚠️ JS 中必须用 .value
}
</script>
<template>
<p>{{ count }}</p> <!-- 模板中自动解包,不用 .value -->
<button @click="increment">+1</button>
</template>
⚠️ Warning:ref 的关键规则
- JS 代码里:访问/修改需要
.value(count.value++) - 模板里:自动解包,直接写
- 这是新手最容易忘的地方!
reactive — 对象类型独家
reactive 创建一个响应式对象,适合包含多个属性的数据。
vue
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: '张三',
age: 25,
hobbies: ['读书', '运动']
})
function growUp() {
user.age++ // ✅ 无需 .value,直接访问属性
}
</script>
<template>
<p>{{ user.name }} - {{ user.age }}岁</p>
<button @click="growUp">长大一岁</button>
</template>
ref vs reactive 全景对比
| | ref | reactive | | 适用类型 | 任何类型,但更推荐基础类型 | 对象、数组 | | 访问方式 | JS 中要 .value,模板中自动解包 | 直接访问属性,无 .value | | 重新赋值 | count.value = 5 ✅ | state = { x: 1 } ❌ 会丢失响应式 | | 解构 | 可以(配合 toRefs) | 不能直接解构(会丢响应式) | | 底层原理 | reactive 包装一层 | Proxy 直接代理 |
computed — 派生数据
和选项式 API 的 computed 一样,但写成函数调用:
vue
<script setup>
import { ref, computed } from 'vue'
const price = ref(100)
const tax = computed(() => price.value * 0.1) // 只读
const total = computed({ // 可读写
get: () => price.value + tax.value,
set: (val) => { price.value = val / 1.1 }
})
</script>
watch — 监听变化
vue
<script setup>
import { ref, watch } from 'vue'
const keyword = ref('')
// 监听单个
watch(keyword, (newVal, oldVal) => {
console.log(`${oldVal} → ${newVal}`)
})
// 监听多个
const a = ref(1)
const b = ref(2)
watch([a, b], ([newA, newB]) => {
console.log(newA, newB)
})
// 深度监听
watch(user, (newVal) => {}, { deep: true })
// 立即执行
watch(keyword, (val) => {}, { immediate: true })
</script>
watchEffect — 自动追踪 + 立即执行
watchEffect 比 watch 更"智能":它自动追踪内部用到的所有响应式数据,并且立即执行一次。
vue
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const doubled = ref(0)
watchEffect(() => {
doubled.value = count.value * 2 // 自动追踪 count
console.log(`count 变了:${count.value}`)
})
// 组件创建时立即执行一次
// 之后 count 每变一次,就执行一次
</script>
watch vs watchEffect:
watch:你知道要监听谁,明确指定watchEffect:你不知道依赖了谁,让 Vue 自己追踪
shallowRef — 浅响应(性能优化)
ref 会深层追踪,对大对象可能浪费性能。shallowRef 只追踪 .value 的变化:
vue
<script setup>
import { shallowRef } from 'vue'
const data = shallowRef({ name: 'Vue', version: 3 })
data.value = { name: 'New' } // ✅ 触发更新(整个 .value 换了)
data.value.name = 'New' // ❌ 不触发(内部属性变化不管)
// 需要强制更新时:
import { triggerRef } from 'vue'
data.value.name = 'New'
triggerRef(data) // 手动触发
</script>
readonly — 只读保护
把数据变成只读,防止意外修改:
vue
<script setup>
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
original.count++ // ✅ 改原始对象可以
copy.count++ // ❌ 警告:只读,不能改
</script>
toRef / toRefs — 保持响应式的解构
reactive 对象解构会丢失响应式,用 toRef / toRefs 保持连接:
vue
<script setup>
import { reactive, toRef, toRefs } from 'vue'
const state = reactive({
name: 'Vue',
version: 3
})
const name = toRef(state, 'name') // 取出单个属性
const { version } = toRefs(state) // 取出多个属性(解构)
// name 和 state.name 保持同步
state.name = 'Vue 3'
console.log(name.value) // 'Vue 3'
</script>
快速选择指南
| 场景 | 用什么 | | 基本类型(数字、字符串、布尔) | ref | | 普通对象 | reactive 或 ref(看个人偏好) | | 根据其他数据计算 | computed | | 监听数据变化做操作 | watch / watchEffect | | 大对象、性能敏感 | shallowRef | | 防止修改 | readonly | | reactive 解构保持响应 | toRef / toRefs | 💡 Tip:一句话口诀
- 基本类型用 ref,对象用 reactive
- 计算用 computed,变化做 watch
- reactive 解构加 toRefs,组件传参记得 defineProps 📝 Note:从选项式迁移过来? 如果你之前学的是选项式 API,建议对着 vue代码风格 里的对照表,把基础篇的概念逐一映射到组合式写法。