---
name: Vue Reactivity System
description: 核心响应式原语 - ref、reactive、computed 与侦听器
---
# Vue 响应式系统
Vue 的响应式系统能够自动追踪状态变更并更新 DOM。
## ref()
使用 `ref()` 创建响应式原始值。在 JavaScript 中通过 `.value` 访问/修改,在模板中自动解包。
```ts
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
```
```vue
```
### 为 ref 添加类型
```ts
import { ref } from 'vue'
import type { Ref } from 'vue'
// 类型推断
const year = ref(2020) // Ref
// 显式泛型
const name = ref(null)
// Ref 类型注解
const id: Ref = ref('abc')
```
## reactive()
创建响应式对象。无需使用 `.value`,但无法重新赋值整个对象。
```ts
import { reactive } from 'vue'
interface State {
count: number
name: string
}
const state: State = reactive({
count: 0,
name: 'Vue'
})
state.count++ // 响应式
```
### reactive() 的局限性
1. **仅适用于对象**——不适用于原始值
2. **无法替换整个对象**——会丢失响应性
3. **解构会丢失响应性**——应使用 `toRefs()`
```ts
const state = reactive({ count: 0 })
// ❌ 丢失响应性
let { count } = state
// ✅ 使用 toRefs
import { toRefs } from 'vue'
const { count } = toRefs(state)
```
## 建议
使用 `ref()` 作为声明响应式状态的首选 API——它适用于任何值类型且行为一致。
## 深层响应性
`ref()` 和 `reactive()` 默认都是深层响应式的:
```ts
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
// 以下操作会触发更新
obj.value.nested.count++
obj.value.arr.push('baz')
```
使用 `shallowRef()` 或 `shallowReactive()` 可退出深层响应式以提升性能。
## DOM 更新时机
DOM 更新是批量且异步进行的。使用 `nextTick()` 等待更新完成:
```ts
import { ref, nextTick } from 'vue'
const count = ref(0)
async function increment() {
count.value++
await nextTick()
// 此时 DOM 已更新
}
```
## Ref 解包规则
- **在模板中**:顶层 ref 自动解包
- **在响应式对象中**:作为属性访问时 ref 自动解包
- **在数组/集合中**:ref 不会自动解包
```ts
const count = ref(0)
const state = reactive({ count })
console.log(state.count) // 0(已解包)
const books = reactive([ref('Vue Guide')])
console.log(books[0].value) // 需要 .value
```
## computed()
从响应式状态派生值,并自动缓存。仅当依赖项发生变化时才重新求值。
```ts
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 只读计算属性
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// 可写计算属性
const fullNameWritable = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue: string) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
```
### 计算属性最佳实践
- **Getter 应保持纯净**——无副作用,不修改其他状态
- **不要直接修改计算属性**——应修改其源数据
- **对于派生数据,优先使用计算属性而非方法**(利用缓存优势)
```ts
// ✅ 已缓存——仅当 items 变化时重新计算
const activeItems = computed(() => items.value.filter(x => x.active))
// ❌ 未缓存——每次渲染都执行
function getActiveItems() {
return items.value.filter(x => x.active)
}
```
## watch()
显式侦听响应式源,在其发生变化时执行副作用。默认惰性求值。
```ts
import { ref, watch } from 'vue'
const id = ref(1)
watch(id, async (newId, oldId) => {
const data = await fetchData(newId)
// 处理数据...
})
```
### 侦听源类型
```ts
const x = ref(0)
const obj = reactive({ count: 0 })
// 单个 ref
watch(x, (newX) => console.log(newX))
// Getter 函数
watch(() => obj.count, (count) => console.log(count))
// 多个源
watch([x, () => obj.count], ([newX, newCount]) => {
console.log(newX, newCount)
})
```
### 侦听选项
```ts
watch(source, callback, {
immediate: true, // 创建时立即执行
deep: true, // 侦听嵌套属性
once: true, // 仅触发一次(3.4+)
flush: 'post' // DOM 更新后执行
})
```
## watchEffect()
自动追踪依赖并立即执行。当任一被追踪的依赖发生变化时重新运行。
```ts
import { ref, watchEffect } from 'vue'
const todoId = ref(1)
const data = ref(null)
watchEffect(async () => {
const response = await fetch(`/api/todos/${todoId.value}`)
data.value = await response.json()
})
```
### watch 与 watchEffect 对比
| 特性 | watch | watchEffect |
| ------------------ | ---------------- | --------------------- |
| 依赖追踪 | 显式指定 | 自动追踪 |
| 惰性求值 | 是 | 否(立即执行) |
| 访问旧值 | 是 | 否 |
| 适用场景 | 特定源 | 多个依赖 |
## 侦听器清理(3.5+)
取消过期的异步操作:
```ts
import { watch, onWatcherCleanup } from 'vue'
watch(id, async (newId) => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal })
onWatcherCleanup(() => controller.abort())
})
```
## 停止侦听器
```ts
const stop = watch(source, callback)
const stop2 = watchEffect(() => { /* ... */ })
// 手动停止
stop()
stop2()
// 暂停/恢复(3.5+)
const { stop, pause, resume } = watchEffect(() => { /* ... */ })
```