模式切换
响应式原理:ref 和 reactive
所谓响应式,即当数据变化时,依赖该数据的视图或逻辑自动更新,无需手动操作 DOM。
Vue 3 提供了两种创建响应式数据的主要方式:ref
和 reactive
。它们在用法和适用场景上有显著区别。
响应式的实现:
- Vue 2:使用
Object.defineProperty
拦截数据,但无法检测新增属性和数组索引的变化。 - Vue 3:改用
Proxy
,支持动态属性增删和数组索引修改,能够实现更细粒度的依赖追踪,性能更高。
核心概念对比
特性 | ref | reactive |
---|---|---|
数据类型 | 适用于基本类型和对象 | 仅适用于对象/数组 |
访问方式 | 需要通过 .value 访问(JS 中) | 直接访问属性 |
模板使用 | 自动解包(无需 .value ) | 直接使用 |
响应性原理 | 内部使用 reactive 包装对象 | 直接使用 Proxy 代理 |
TS 类型支持 | 更简单 | 需要定义接口 |
适用场景 | 基本类型、需要显式控制的变量 | 复杂对象、不需要 .value 的场合 |
ref 深度解析
基本用法
javascript
import { ref } from 'vue'
// 基本类型
const count = ref(0)
// 对象类型(内部会自动用 reactive 转换)
const user = ref({
name: 'Alice',
age: 25
})
// 修改值(JS中需要 .value)
count.value++
user.value.name = 'Bob'
模板中的自动解包
html
<template>
<!-- 基本类型:自动解包 -->
<div>{{ count }}</div> <!-- 不需要 .value -->
<!-- 对象类型:属性访问也不需要 .value -->
<div>{{ user.name }}</div>
</template>
为什么需要 .value?
ref
通过包装对象实现响应性:
javascript
// 伪代码实现
function ref(value) {
return {
get value() { track(this, 'value'); return value },
set value(newVal) { value = newVal; trigger(this, 'value') }
}
}
适用场景
- 基本类型数据(string/number/boolean)
- 需要显式控制响应式引用的场合
- 需要重新赋值的变量(因为
reactive
不能替换整个对象)
reactive 深度解析
基本用法
javascript
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: 'Alice'
}
})
// 直接修改属性
state.count++
state.user.name = 'Bob'
限制与特性
不能替换整个对象
javascript// ❌ 错误:会失去响应性 state = { count: 1 } // ✅ 正确:修改属性 Object.assign(state, { count: 1 })
对基本类型无效
javascript// ❌ 不会变成响应式 const count = reactive(0)
适用场景
- 复杂的嵌套对象
- 不需要重新赋值的状态管理
- 表单对象等结构化数据
转换与工具函数
toRefs:保持响应式解构
javascript
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
name: 'Alice'
})
// 解构后保持响应性
const { count, name } = toRefs(state)
count.value++ // 仍然响应
toRef:单个属性转换
javascript
const countRef = toRef(state, 'count')
isRef/isReactive 类型检查
javascript
import { isRef, isReactive } from 'vue'
console.log(isRef(count)) // true
console.log(isReactive(state)) // true
性能与实现差异
实现原理
ref
:使用对象包装 +reactive
javascriptclass RefImpl { constructor(value) { this._value = isObject(value) ? reactive(value) : value } get value() { /* 依赖收集 */ } set value(newVal) { /* 触发更新 */ } }
reactive
:直接使用 ES6 Proxy
性能考虑
ref
对基本类型有轻微性能优势reactive
更适合复杂对象,减少.value
使用
最佳实践指南
基础规则:
- 基本类型 →
ref
- 对象/数组 →
reactive
- 需要重新赋值 →
ref
- 基本类型 →
项目一致性:
- 团队统一选择一种风格(特别是对象处理)
- 推荐组合式 API 优先使用
ref
TypeScript 优化:
typescript// ref 类型推断更简单 const count = ref<number>(0) // reactive 需要接口 interface State { count: number user: User } const state = reactive<State>({...})
组合式函数:
- 返回响应式数据时优先返回
ref
,方便使用者解构
- 返回响应式数据时优先返回
常见问题解答
Q1:为什么有时候需要 .value
,有时候不需要?
- 在
<template>
和reactive
对象中自动解包 - 在 JS 逻辑中需要显式使用
.value
Q2:ref
可以替代 reactive
吗?
技术上可以,但会导致代码中大量 .value
,降低可读性
Q3:如何选择大型项目的状态结构?
- 顶层状态用
reactive
组织模块 - 组件内部简单状态用
ref
总结
ref
和 reactive
是 Vue 3 响应式系统的两大支柱:
ref
是通用解决方案,适合大多数场景reactive
为复杂对象提供更简洁的语法- 理解它们的差异能帮助开发者写出更高效、可维护的代码