vue 3 ref原理
Vue 3 的 ref
主要用于包装基础类型(如 number/string
)使其成为响应式对象,同时也能处理对象/数组(此时内部转为 reactive
)。以下是其核心实现思路及关键代码逻辑:
核心设计目标
- 基础类型支持:通过对象包装让原始值具备响应式能力。
- 统一访问接口:用
.value
属性统一操作值,简化心智模型。 - 自动解包优化:在模板中自动解包
.value
,提升开发体验。
核心实现结构
构造函数 RefImpl
class RefImpl<T> {
private _value: T
private _rawValue: T // 存储原始值,用于比对变化
public dep: Dep = new Set() // 依赖集合
public __v_isRef = true // 标识是 ref 对象
constructor(value: T) {
this._rawValue = value
// 如果值是对象,用 reactive 包裹
this._value = isObject(value) ? reactive(value) : value
}
get value() {
trackRefValue(this) // 收集依赖(与 reactive 共享 targetMap)
return this._value
}
set value(newValue) {
// 新旧值对比(需处理对象/NaN等特殊情况)
if (hasChanged(newValue, this._rawValue)) {
this._rawValue = newValue
this._value = isObject(newValue) ? reactive(newValue) : newValue
triggerRefValue(this) // 触发依赖更新
}
}
}
关键函数实现
创建 ref (ref()
)
function ref(value) {
// 已经是 ref 则直接返回
if (isRef(value)) return value;
// 包裹基本类型/对象
return new RefImpl(value);
}
依赖收集 (trackRefValue
)
function trackRefValue(ref) {
if (activeEffect) {
trackEffects(ref.dep || (ref.dep = new Set()));
}
}
触发更新 (triggerRefValue
)
function triggerRefValue(ref) {
if (ref.dep) {
triggerEffects(ref.dep);
}
}
判断 Ref (isRef
)
function isRef(r) {
return !!(r && r.__v_isRef === true);
}
技术要点解析
-
自动对象转换
当ref
接受对象时,内部自动调用reactive
:const objRef = ref({ count: 0 }) objRef.value.count++ // 触发更新(因内部是 reactive)
-
值变更比对优化
使用Object.is()
处理NaN
与+0/-0
的特殊情况:function hasChanged(newVal, oldVal) { return !Object.is(newVal, oldVal); }
-
模板自动解包
在模板中直接使用ref
会省略.value
:<template> <!-- 直接写 count 而非 count.value --> <div>{{ count }}</div> </template> <script setup> const count = ref(0) // 编译时自动解包 </script>
-
响应式解包机制
当ref
嵌套在reactive
对象中时,自动解包.value
:const r = ref(0); const obj = reactive({ r }); console.log(obj.r); // 输出 0,而非 ref 对象
边界处理与工具函数
-
unref
工具函数:解包ref
或返回原值function unref(ref) { return isRef(ref) ? ref.value : ref; }
-
处理只读
ref
(readonly(ref(0))
):
通过shallowReadonly
阻断.value
修改:function readonlyRef(ref) { return Object.freeze({ get value() { return ref.value } }); }
-
自定义 Ref (
customRef
):
手动控制依赖跟踪与触发逻辑:function customRef(factory) { class CustomRefImpl extends RefImpl { constructor(getter, setter) { super(undefined); // 用户自定义 track/trigger 逻辑 } } return new CustomRefImpl(factory()); }
性能优化
-
惰性依赖收集
仅当在effect
中访问.value
时才收集依赖,避免不必要的内存开销。 -
轻量级对象包装
RefImpl
类比reactive
的Proxy
更轻量,尤其适合基础类型。 -
复用依赖存储结构
共用全局的targetMap
(WeakMap
) 来管理所有响应式对象的依赖关系。
与 Reactive 的关系对比
特性 | ref |
reactive |
---|---|---|
主要用途 | 包装基础类型或任意对象 | 包装对象/数组 |
访问方式 | .value |
直接访问属性 |
模板解包 | 自动解包(除嵌套在对象内部) | 无特殊处理 |
内部实现 | getter/setter + 依赖集合 |
Proxy + 递归代理 |
性能对比 | 更高频次基础类型操作的优化 | 适合复杂对象结构 |
使用示例
// 基础类型
const count = ref(0)
effect(() => console.log(count.value)) // -> 0
count.value++ // 触发 effect -> 1
// 对象类型
const user = ref({ name: 'Alice' })
user.value.name = 'Bob' // 触发更新(内部是 reactive)
// DOM 元素引用
const inputRef = ref(null)
onMounted(() => inputRef.value.focus())
总结
ref
通过包装对象 + 访问器(getter/setter)实现了对各类值的响应式支持,其核心思想是:将基础类型转化为引用类型以利用响应式系统。设计 .value
的访问方式虽然增加了心智负担,但换来更灵活的类型支持与模板优化,成为组合式 API 的基础工具之一。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY