在 Vue 3 的响应式系统中,ref 和 reactive 是用于定义响应式变量最核心的两个API。今天本文将带大家剖析 ref 的内部机制,以及与 reactive 的对比和应用场景。
一、什么是 ref
ref 是 Vue 3 提供的一个函数,用于创建一个可响应式的引用对象,它的值通过 .value 属性访问和修改:
javascript
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
内部实现简述
ref 返回的是一个包含 value getter/setter 的对象(如 RefImpl类实例)。当读取 .value 时,会触发getter下的track() 收集依赖;当赋值时,会触发 setter中的trigger() 触发更新。
javascript
/*
* - RefImpl类实现get和set
* @params {any} rawValue - ref传入的值
* @params {boolean|readonly} _isShallow - 判断是否浅层代理决定是否需要reactive
* @returns - 无
*/
class RefImpl {
constructor(rawValue, _isShallow) {
this._value = _isShallow ? _rawValue: convert(_rawValue)
this._shallow = _isShallow
}
get value() {
track(this, 'value') // 收集依赖
return this._value
}
set value(newValue) {
if (hasChanged(newValue, this._value)) {//旧值与新值比对
this._value = this._shallow ? newValue : convert(newValue);
trigger(this, 'value') // 触发更新
}
}
}
function convert(value) {
return (Object.prototype.toString.call(value) == '[object Object]') ? reactive(value) : value
}
说明:ref 的响应式依赖对 .value 的访问
二、ref vs reactive 核心区别
适用类型:
ref: 基本类型(number, string, boolean)或对象
reactive: 仅对象/数组(不能用于基本类型)
访问方式:
ref: 必须通过 .value
reactive: 直接属性访问(如 obj.count)
解包行为
ref: 在模板中自动 .value 解包
reactive: 无解包,直接使用
嵌套响应式
ref: 对象会自动转为 reactive(除非 shallow)
reactive: 所有嵌套对象进行响应式转换
javascript
示例对比
// 使用 ref(推荐用于基本类型)
const count = ref(0)
const user = ref({ name: 'Alice' })
// 使用 reactive
const state = reactive({
count: 0,
user: { name: 'Alice' }
})
//在模板中
<template>
{{ count }}
{{ user.name }}
{{ state.count }}
{{ state.user.name }}
</template>
说明:在 JavaScript 中,ref 必须手动 .value,而 reactive 不需要。
三、何时使用 ref,何时使用 reactive?
推荐使用 ref 的场景:
如果是处理基本类型数据,则使用ref,这也是 reactive 无法做到的, reactive处理基本类型数据无响应式更新,例如:
javascript
let val = reactive(1)
// 以下更改都会失去响应式
setTimeout(() => {
val = 2
val = reactive(2)
}, 1000)
推荐使用 reactive 的场景:
javascript
// 管理复杂对象或表单状态
const form = reactive({
email: '',
password: '',
errors: []
})
//避免大量 .value 写法
// 比 ref({ ... }) 更简洁
const state = reactive({ a: 1, b: 2, c: 3 })
state.a++ // 直接操作
//性能略优于 ref 的嵌套对象
//reactive 只创建一层 Proxy,而 ref 包裹对象时内部仍会调用reactive
四、常见误区
1:在 effect 或 watchEffect 中创建 ref
javascript
// 错误,每次 effect 执行都会新建 ref,失去响应式
effect(() => {
const count = ref(0)
console.log(count.value)
})
// 正确做法:在 effect 外定义 ref
const count = ref(0)
effect(() => {
console.log(count.value) // 追踪稳定的 ref
})
2:混淆 ref 和普通变量
javascript
let count = ref(0)
count = 1 // 赋值给变量本身,丢失响应式!
//正确:始终操作 .value
count.value = 1
五、总结
对于掌握 ref 与 reactive 的差异,可以在适当场景下写出高效、可维护 Vue 3 应用的关键。本文主要基于ref的核心源码实现,其他功能如reactive和effect的依赖与收集实现可以参考本人之前的文章,也欢迎有问题的博友在评论区一起讨论!