前言
ref
和reactive
是Vue3
的Composition API
里最常用的两个核心成员,专门用来创建响应式数据的。 啥叫响应式呢?就是数据一变,页面自动跟着更新,不用你手动刷新。 其实它们干的是一件事就是:让Vue能监听到你的数据变化。
但它们用起来不一样,适合的场景也不一样。很多人在用的时候都是凭感觉选,到底该用哪个?一直都觉得很模糊。
先简单认识一下:
ref :可以包装任何类型的值,包括基本类型(数字、字符串等)和对象类型。使用时需要通过.value
来访问和修改值。 reactive :只能包装对象类型(包括数组)。使用时直接访问属性即可,不需要.value
。

下面咱们通过实际例子来看一下。
一、区别
javascript
// ref 啥都能包
const count = ref(0) // 数字
const name = ref('小明') // 字符串
const isActive = ref(false) // 布尔值
const user = ref({ age: 18 }) // 对象
const list = ref([1, 2, 3]) // 数组
// reactive 只接对象/数组
const user = reactive({ age: 18 }) // 对象
const list = reactive([1, 2, 3]) // 数组
const count = reactive(0) // 报错
二、优缺点
ref
优点:
- 什么都能包:基本类型、对象、数组,来者不拒
- 赋值简单 :直接
xxx.value = newValue
- 解构安全:不用怕响应式丢失
- 类型推导好 :
TypeScript
支持完美
缺点:
- 总要写 .value:有点烦人
- 模板中也要 .value:不过 Vue 会自动解包
reactive
优点:
- 不用写 .value:直接访问属性
- 相关联数据组织性好 :类似
Vue2
的data
缺点:
- 解构大坑:直接解构容易丢失响应式
- 赋值限制:不能整个重新赋值
- 类型推导有时抽风:TS环境下偶尔会出现问题
三、watch监听中的差异
这里是最容易踩坑的地方!
监听 ref
javascript
const count = ref(0)
const user = ref({ name: '小明', age: 18 })
// 监听基本类型 ref
watch(count, (newVal, oldVal) => {
console.log('count变化:', newVal, oldVal)
})
// 监听对象 ref - 需要深度监听
watch(user, (newVal, oldVal) => {
console.log('user变化:', newVal, oldVal)
}, { deep: true }) // 必须加 deep!
// 监听对象 ref 的特定属性
watch(() => user.value.name, (newVal, oldVal) => {
console.log('name变化:', newVal, oldVal)
})
监听 reactive
javascript
const state = reactive({
count: 0,
user: { name: '小明', age: 18 }
})
// 自动深度监听,不需要 deep: true
watch(state, (newVal, oldVal) => {
console.log('state变化:', newVal, oldVal)
})
// 推荐:监听特定属性
watch(() => state.count, (newVal) => {
console.log('count变了', newVal)
})
// 监听嵌套属性
watch(() => state.user.name, (newVal) => {
console.log('名字变了', newVal)
})
// 监听多个属性
watch([() => state.count, () => state.user.name], ([newCount, newName]) => {
console.log('count或name变了', newCount, newName)
})
watch 的重要区别
1.深度监听:
ref
对象需要手动{ deep: true }
reactive
自动深度监听
2.旧值获取:
reactive
的旧值和新值相同(Proxy
特性)ref
的旧值正常
3.性能影响:
reactive
自动深度监听,可能影响性能ref
可以精确控制监听深度
四、案例
案例1:表单处理 - 推荐 reactive
javascript
// 相关联的表单数据,用 reactive 更合适
const form = reactive({
username: '',
password: '',
remember: false,
errors: {}
})
// 验证函数
const validateForm = () => {
form.errors = {}
if (!form.username) {
form.errors.username = '用户名不能为空'
}
// ...其他验证
}
案例2:API 数据加载 - 推荐 ref
javascript
// API 返回的数据,经常需要重新赋值,用 ref
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
loading.value = true
try {
const response = await fetch('/api/data')
data.value = await response.json() // 直接赋值,美滋滋
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
案例3:组件状态管理 - 看情况
javascript
// 方案1:用 reactive(状态相关联)
const modal = reactive({
isOpen: false,
title: '',
content: '',
loading: false
})
// 方案2:用多个 ref(状态相对独立)
const isModalOpen = ref(false)
const modalTitle = ref('')
const modalContent = ref('')
const modalLoading = ref(false)
案例4:列表操作 - 强烈推荐 ref
javascript
const list = ref([])
// 添加项目
const addItem = (item) => {
list.value = [...list.value, item] // 重新赋值,安全
}
// 删除项目
const removeItem = (id) => {
list.value = list.value.filter(item => item.id !== id)
}
// 清空列表
const clearList = () => {
list.value = [] // 直接赋值,不会丢失响应式
}
如果用reactive
来做列表:
javascript
const list = reactive([])
// 添加项目 - 只能用 push
const addItem = (item) => {
list.push(item) // 可以,但不够直观
}
// 删除项目 - 需要找到索引
const removeItem = (id) => {
const index = list.findIndex(item => item.id === id)
if (index !== -1) {
list.splice(index, 1) // 有点麻烦
}
}
// 清空列表 - 只能修改长度
const clearList = () => {
list.length = 0 // 能工作,但有点 hack
}
五、组合式函数中的选择
这是决定用哪个的关键场景!
返回多个 ref:灵活好用
javascript
function useCounter(initialValue = 0) {
const count = ref(initialValue)
const double = computed(() => count.value * 2)
const increment = () => count.value++
return { count, double, increment }
}
// 使用时:
const { count, double, increment } = useCounter()
返回 reactive:结构固定
javascript
function useUser() {
const state = reactive({
user: null,
loading: false,
error: null
})
const fetchUser = async (id) => {
state.loading = true
try {
state.user = await fetchUserById(id)
} catch (err) {
state.error = err.message
} finally {
state.loading = false
}
}
return { ...toRefs(state), fetchUser } // 必须用 toRefs!
}
// 使用时:
const { user, loading, error, fetchUser } = useUser()
明显看到,返回多个ref
更简单直接。
六、混合使用模式(推荐)
javascript
// 基本类型和需要重新赋值的用 ref
const loading = ref(false)
const error = ref(null)
const data = ref(null)
// 相关联的数据用 reactive
const form = reactive({
username: '',
password: '',
remember: false
})
// 这样写,既清晰又安全
七、性能
其实在大多数情况下,性能差异可以忽略不计,推荐如下:
- 大量基本类型 :用
ref
,因为reactive
需要创建Proxy
对象 - 大对象但只关心部分属性 :用多个
ref
,避免不必要的深度监听 - 频繁重新赋值 :用
ref
,reactive
不能直接重新赋值
总结
优先使用ref
的场景: -基本类型(数字、字符串、布尔值) -DOM 引用和组件引用 -需要频繁重新赋值的变量 -API 返回的数据 -组合式函数的返回值 -列表数据
考虑使用reactive
的场景: -相关联的表单数据 -复杂的组件状态 -配置对象 -不需要重新赋值的对象
希望这篇分析对你有帮助。如果有不同意见,欢迎在评论区理性讨论!
公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》
《Java 订单超时未支付,如何自动关闭?掌握这 3 种方案,轻松拿 offer!》