Vue3提供了多种创建响应式数据的方式,主要包括
- reactive()(返回代理对象,适用于复杂数据结构)
- ref()(返回Ref对象,包装原始值)
- computed()(返回计算属性Ref)
不同类型具有不同的响应式特点:reactive实现深度响应,ref需要.value访问,computed具有惰性求值特性。
此外还有readonly、shallow系列等变体,分别适用于只读、浅层响应等场景。
类型判断可使用isRef/isReactive工具函数。
开发者应根据具体需求(处理原始值/对象、性能优化、只读需求等)选择合适的响应式API。
Vue 3 响应式系统类型关系总结
| 类别 | 核心API | 返回类型 | 响应性特点 | 适用场景 | 注意事项 |
|---|---|---|---|---|---|
| 响应式对象 | reactive() | T(代理对象) | 深度响应式,嵌套属性自动响应 | 对象、数组、Set、Map等复杂数据结构 | 1. 仅适用于对象类型 2. 不能用于原始值 3. 解构会丢失响应性 |
| 响应式原始值 | ref() | Ref<T> | 包装原始值为响应式对象,通过.value访问 | 原始值(string、number、boolean等)、对象引用 | 1. 需通过.value访问/修改值2. 模板中自动解包 |
| 计算属性 | computed() | ComputedRef<T> | 惰性求值,缓存结果,依赖变化时重新计算 | 派生状态、复杂计算逻辑 | 1. 避免副作用 2. 结果只读(除非提供setter) |
| 只读响应式 | readonly() | Readonly<T> | 创建只读代理,防止意外修改 | 传递响应式对象但不允许修改的场景 | 1. 嵌套属性也是只读的 2. 深层只读 |
| 浅层响应式 | shallowRef() | ShallowRef<T> | 仅.value本身响应式,嵌套对象不处理 | 大型对象或与第三方库集成 | 1. 替换整个.value会触发响应2. 修改嵌套属性不会 |
| 浅层响应式对象 | shallowReactive() | T | 仅根级别属性响应式,嵌套对象保持原样 | 性能敏感场景,明确知道哪些属性需要响应 | 仅跟踪根级别属性的访问/修改 |
| 浅层只读 | shallowReadonly() | Readonly<T> | 根级别只读,嵌套对象可修改 | 需要部分保护的情况 | 允许修改深层嵌套属性 |
| 响应式转换 | toRef() | Ref<T> | 为响应式对象的属性创建单独ref | 提取props的属性保持响应性 | 保持与源属性的响应式连接 |
| 响应式转换(多个) | toRefs() | { [K in keyof T]: Ref<T[K]> } | 将响应式对象转为普通对象,属性均为ref | 解构reactive对象时保持响应性 | 便于解构而不丢失响应性 |
| 原始值转换 | toRaw() | T | 返回reactive或readonly的原始对象 | 需要操作非代理对象时(如第三方库) | 绕过响应式系统,修改不会触发更新 |
| 标记响应式 | markRaw() | T | 标记对象永不转换为响应式 | 第三方类实例、大型不可变数据 | Vue会跳过对该对象的代理 |
类型关系图
text
原始数据 ├── reactive() → 响应式代理 (深度) │ ├── readonly() → 只读代理 │ └── toRefs() → 解构用ref集合 ├── ref() → Ref对象 (包装值) │ ├── shallowRef() → 浅层Ref │ └── computed() → 计算Ref ├── shallowReactive() → 浅层代理 │ └── shallowReadonly() → 浅层只读代理 └── markRaw() → 非响应式标记
选择指南
使用
reactive当处理对象/集合且需要深度响应使用
ref当处理原始值或需要替换整个对象引用使用
computed当值依赖其他响应式数据使用
readonly当需要防止意外修改使用
shallow*系列优化性能或集成外部库使用
toRefs当需要解构reactive对象
TypeScript 类型关系示例
// 基本类型关系 type Reactive<T> = T extends object ? T : never type Ref<T> = { value: T } type ComputedRef<T> = Readonly<Ref<T>> // 实际使用 const state = reactive({ count: 0 }) // Reactive<{ count: number }> const count = ref(0) // Ref<number> const doubled = computed(() => count.value * 2) // ComputedRef<number>这个总结涵盖了Vue 3响应式系统的主要API及其类型关系,帮助开发者根据具体场景选择合适的响应式工具。
在 Vue 3 中,响应式值的类型不一定是Ref,但使用ref()创建的响应式变量确实是Ref<T>类型。
Vue 3 提供了多种创建响应式数据的方式,每种方式对应不同的类型:
1.ref()→ 类型是Ref<T>
用于创建基本类型(如number、string、boolean)或普通对象的响应式引用。
import { ref } from 'vue' const count = ref(0) // Ref<number> const name = ref('Alice') // Ref<string> const obj = ref({ a: 1 }) // Ref<{ a: number }>- 访问/修改需要
.value:console.log(count.value) // 0 count.value++ // 修改
2.reactive()→ 类型是原始对象的代理(Proxy),不是Ref
用于创建对象或数组的深层响应式副本。
import { reactive } from 'vue' const state = reactive({ count: 0, name: 'Alice' }) // { count: number; name: string }- 直接访问属性,不需要
.value:console.log(state.count) // 0 state.count++ // 修改
⚠️ 注意:
reactive()返回的是一个 Proxy 对象,其 TypeScript 类型就是你传入对象的类型(如{ count: number }),不是Ref。
3.shallowRef()/shallowReactive()→ 浅层响应式
shallowRef<T>()→Ref<T>(但只对.value本身响应,内部对象属性非响应式)shallowReactive<T>(obj)→T(只对顶层属性响应)
4. 在<script setup>中配合defineModel、props等
props是reactive对象(不可变,类型为Readonly<...>)defineModel()返回的是Ref
如何判断一个值是不是Ref?
使用 Vue 提供的工具函数:
import { isRef, isReactive } from 'vue' isRef(count) // true(如果是 ref 创建的) isReactive(state) // true(如果是 reactive 创建的)总结
| 创建方式 | 返回类型 | 是否需要.value | 适用场景 |
|---|---|---|---|
ref(value) | Ref<T> | ✅ 是 | 基本类型、单个值 |
reactive(obj) | T(Proxy 代理的对象) | ❌ 否 | 对象、数组等复杂结构 |
shallowRef() | Ref<T>(浅层) | ✅ 是 | 大型对象性能优化 |
shallowReactive() | T(浅层 Proxy) | ❌ 否 | 仅需顶层响应式 |
所以:只有通过
ref()(及其变体如computed、shallowRef)创建的响应式变量才是Ref类型;而reactive()创建的是普通对象类型的响应式代理,不是Ref。
补充:computed也是Ref
import { computed } from 'vue' const doubled = computed(() => count.value * 2) // ComputedRef<number>(是 Ref 的子类型)computed返回的是ComputedRef<T>,它继承自Ref<T>,所以也可以用.value访问,并且isRef(doubled)为true。