ref 底层原理
更新: 10/16/2025 字数: 0 字 时长: 0 分钟
ref 是 Vue 3 响应式系统的核心 API 之一,用于创建响应式的基本类型值。本文将深入解析 ref 的实现原理,包括 ref、shallowRef、triggerRef、customRef 等相关 API。
ref 的设计思想
为什么需要 ref
JavaScript 的基本类型(number、string、boolean 等)是按值传递的,无法被 Proxy 代理:
typescript
// Proxy 只能代理对象
let count = 0
const proxy = new Proxy(count, handlers) // ❌ 错误
//需要将基本类型包装成对象
let count = { value: 0 }
const proxy = new Proxy(count, handlers) // ✅ 正确ref 的本质就是将基本类型包装成一个对象,使其可以被 Proxy 代理。
ref 与 reactive 的关系
核心要点:ref 的底层就是 reactive,reactive 是对象代理。
typescript
// ref 的实现可以简化理解为:
function ref(value) {
// 创建一个包装对象
const wrapper = {
value: value
}
// 如果 value 是对象,使用 reactive 转换
if (isObject(value)) {
wrapper.value = reactive(value)
}
// 返回包装对象(实际上会被 RefImpl 类处理)
return wrapper
}
// 所以 ref 本质上是:
// 1. 将值包装成 { value: xxx } 对象
// 2. 如果值是对象,内部使用 reactive 进行响应式转换
// 3. RefImpl 类负责拦截 .value 的访问和修改关系示意图
基本类型值
↓
ref() 包装
↓
RefImpl { value: 基本类型值 }
↓
通过 getter/setter 拦截 .value 访问
↓
依赖收集和触发更新
对象类型值
↓
ref() 包装
↓
RefImpl { value: reactive(对象) }
↓
内部值使用 reactive 代理
↓
.value 访问返回代理对象ref vs reactive
typescript
// ref - 适用于基本类型,但底层对象值使用 reactive
const count = ref(0)
console.log(count.value) // 需要 .value 访问
// ref 包装对象
const user = ref({ name: 'Alice', age: 25 })
// 等价于:RefImpl { value: reactive({ name: 'Alice', age: 25 }) }
user.value.name = 'Bob' // 内部的对象是 reactive 代理的,可以响应式更新
// reactive - 直接代理对象
const state = reactive({ count: 0 })
console.log(state.count) // 直接访问属性
// 两者的关系
const refObj = ref({ count: 0 })
const reactiveObj = reactive({ count: 0 })
// refObj.value 实际上就是一个 reactive 对象
console.log(isReactive(refObj.value)) // true
console.log(isReactive(reactiveObj)) // trueref 的实现
RefImpl 类
typescript
// ref 的实现类
class RefImpl<T> {
private _value: T
private _rawValue: T
// 依赖集合
public dep?: Dep = undefined
// 标记为 ref
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
// 保存原始值
this._rawValue = __v_isShallow ? value : toRaw(value)
// 如果是对象且不是 shallow,转换为 reactive
this._value = __v_isShallow ? value : toReactive(value)
}
// getter - 依赖收集
get value() {
// 收集依赖
trackRefValue(this)
return this._value
}
// setter - 触发更新
set value(newVal) {
// 判断是使用新值还是原始值进行比较
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
// 值发生变化时才触发更新
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
// 触发更新
triggerRefValue(this, newVal)
}
}
}
// ⭐ 核心:转换为响应式对象
// 这里体现了 ref 底层使用 reactive 的关键
const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
// 如果值是对象,使用 reactive 进行代理
// 如果是基本类型,直接返回
// ref 工厂函数
export function ref<T = any>(value?: unknown): Ref<UnwrapRef<T>> {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
// 如果已经是 ref,直接返回
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
// 示例:ref 如何处理不同类型的值
const num = ref(0) // RefImpl { value: 0 }
const str = ref('hello') // RefImpl { value: 'hello' }
const obj = ref({ a: 1 }) // RefImpl { value: reactive({ a: 1 }) }
const arr = ref([1, 2, 3]) // RefImpl { value: reactive([1, 2, 3]) }依赖收集
typescript
// 收集 ref 的依赖
export function trackRefValue(ref: RefBase<any>) {
// 如果应该追踪且有活跃的 effect
if (shouldTrack && activeEffect) {
// 获取原始 ref
ref = toRaw(ref)
// 如果还没有 dep,创建一个
if (!ref.dep) {
ref.dep = createDep()
}
// 追踪依赖
trackEffects(ref.dep)
}
}
export function trackEffects(dep: Dep) {
let shouldTrack = false
// 检查是否需要追踪
if (effectTrackDepth <= maxMarkerBits) {
// 使用位标记优化
if (!newTracked(dep)) {
// 标记为新追踪
dep.n |= trackOpBit
// 如果已经被追踪过,不需要重复追踪
shouldTrack = !wasTracked(dep)
}
} else {
// 降级处理
shouldTrack = !dep.has(activeEffect!)
}
if (shouldTrack) {
// 将 effect 添加到 dep
dep.add(activeEffect!)
// 将 dep 添加到 effect
activeEffect!.deps.push(dep)
}
}触发更新
typescript
// 触发 ref 的更新
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref)
// 如果有依赖,触发更新
if (ref.dep) {
triggerEffects(ref.dep)
}
}
export function triggerEffects(dep: Dep | ReactiveEffect[]) {
// 转换为数组
const effects = isArray(dep) ? dep : [...dep]
// 先触发计算属性的 effect
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect)
}
}
// 再触发普通 effect
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect)
}
}
}
function triggerEffect(effect: ReactiveEffect) {
// 避免无限递归
if (effect !== activeEffect || effect.allowRecurse) {
if (effect.scheduler) {
// 使用调度器
effect.scheduler()
} else {
// 直接执行
effect.run()
}
}
}shallowRef
shallowRef 只对 .value 的访问是响应式的,对于 .value 的属性访问不是响应式的。
实现
typescript
export function shallowRef<T = any>(value?: T): ShallowRef<T> {
return createRef(value, true)
}
// 使用示例
const state = shallowRef({ count: 0 })
// 修改 value 会触发更新
state.value = { count: 1 } // ✅ 触发更新
// 修改 value 的属性不会触发更新
state.value.count = 2 // ❌ 不触发更新应用场景
typescript
// 1. 性能优化 - 避免深层响应式转换
const largeObject = shallowRef({
// 大量嵌套数据
nested: {
deep: {
value: 1
}
}
})
// 只在需要时整体替换
largeObject.value = newLargeObject
// 2. 集成第三方库
const echarts = shallowRef(null)
onMounted(() => {
echarts.value = echarts.init(el)
})triggerRef
强制触发 ref 的更新,即使值没有变化。
实现
typescript
export function triggerRef(ref: Ref) {
triggerRefValue(ref, ref.value)
}
// 使用示例 - 配合 shallowRef
const state = shallowRef({ count: 0 })
// 修改属性不会自动触发更新
state.value.count++
// 手动触发更新
triggerRef(state)customRef
创建一个自定义的 ref,显式控制依赖追踪和触发更新。
实现
typescript
class CustomRefImpl<T> {
public dep?: Dep = undefined
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
public readonly __v_isRef = true
constructor(factory: CustomRefFactory<T>) {
// 工厂函数返回 get 和 set
const { get, set } = factory(
() => trackRefValue(this),
() => triggerRefValue(this)
)
this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
return new CustomRefImpl(factory) as any
}应用场景
1. 防抖 ref
typescript
function useDebouncedRef<T>(value: T, delay = 200) {
let timeout: number
return customRef<T>((track, trigger) => {
return {
get() {
// 依赖收集
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
// 触发更新
trigger()
}, delay)
}
}
})
}
// 使用
const text = useDebouncedRef('hello')
// 连续修改,只会在最后一次修改后 200ms 触发更新
text.value = 'h'
text.value = 'he'
text.value = 'hel'
text.value = 'hell'
text.value = 'hello'2. 节流 ref
typescript
function useThrottledRef<T>(value: T, delay = 200) {
let timeout: number | undefined
let lastTrigger = 0
return customRef<T>((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
const now = Date.now()
if (now - lastTrigger >= delay) {
// 立即触发
value = newValue
lastTrigger = now
trigger()
} else {
// 延迟到时间间隔后触发
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
lastTrigger = Date.now()
trigger()
}, delay - (now - lastTrigger))
}
}
}
})
}3. 双向绑定 ref
typescript
function useVModel<T>(
props: any,
key: string,
emit: any
) {
return customRef<T>((track, trigger) => {
return {
get() {
track()
return props[key]
},
set(newValue) {
emit(`update:${key}`, newValue)
trigger()
}
}
})
}
// 在组件中使用
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const value = useVModel(props, 'modelValue', emit)
return { value }
}
}toRef 和 toRefs
toRef - 为响应式对象的属性创建 ref
typescript
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K]
) {}
get value() {
const val = this._object[this._key]
return val === undefined ? this._defaultValue! : val
}
set value(newVal) {
this._object[this._key] = newVal
}
}
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue?: T[K]
): ToRef<T[K]> {
const val = object[key]
return isRef(val) ? val : (new ObjectRefImpl(object, key, defaultValue) as any)
}
// 使用示例
const state = reactive({
foo: 1,
bar: 2
})
// 为 foo 创建一个 ref,保持响应式连接
const fooRef = toRef(state, 'foo')
// 修改 ref 会影响原对象
fooRef.value++
console.log(state.foo) // 2
// 修改原对象也会影响 ref
state.foo++
console.log(fooRef.value) // 3toRefs - 为响应式对象的所有属性创建 ref
typescript
export function toRefs<T extends object>(object: T): ToRefs<T> {
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
// 使用示例 - 解构响应式对象
const state = reactive({
foo: 1,
bar: 2
})
// 直接解构会失去响应式
const { foo, bar } = state // ❌ foo 和 bar 不再是响应式的
// 使用 toRefs 保持响应式
const { foo: fooRef, bar: barRef } = toRefs(state) // ✅ 保持响应式
// 在模板中使用
export default {
setup() {
const state = reactive({
foo: 1,
bar: 2
})
// 返回 refs,模板中会自动解包
return {
...toRefs(state)
}
}
}isRef 和 unref
isRef - 检查是否为 ref
typescript
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return !!(r && r.__v_isRef === true)
}
// 使用
const count = ref(0)
const state = reactive({ count: 0 })
console.log(isRef(count)) // true
console.log(isRef(state)) // false
console.log(isRef(state.count)) // falseunref - 获取 ref 的值
typescript
export function unref<T>(ref: T | Ref<T>): T {
return isRef(ref) ? ref.value : ref
}
// 等价于
const value = isRef(ref) ? ref.value : ref
// 使用示例
function useFeature(maybeRef: number | Ref<number>) {
// 统一处理 ref 和普通值
const value = unref(maybeRef)
console.log(value)
}
useFeature(10) // 10
useFeature(ref(10)) // 10proxyRefs - 自动解包
在模板中,ref 会被自动解包,这是通过 proxyRefs 实现的。
实现
typescript
export function proxyRefs<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
// Proxy handlers
const shallowUnwrapHandlers: ProxyHandler<any> = {
get(target, key, receiver) {
// 自动解包 ref
return unref(Reflect.get(target, key, receiver))
},
set(target, key, value, receiver) {
const oldValue = target[key]
// 如果旧值是 ref 而新值不是,更新 ref.value
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
} else {
return Reflect.set(target, key, value, receiver)
}
}
}
// Vue 组件的 setup 返回值会被 proxyRefs 处理
export default {
setup() {
const count = ref(0)
return {
count // 在模板中可以直接用 count,不需要 count.value
}
}
}computed 的实现
computed 本质上也是一个特殊的 ref。
ComputedRefImpl 类
typescript
class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly __v_isReadonly: boolean
// 是否需要重新计算
public _dirty = true
// 是否正在计算
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
// 创建 effect
this.effect = new ReactiveEffect(getter, () => {
// 当依赖变化时,标记为 dirty
if (!this._dirty) {
this._dirty = true
// 触发计算属性的更新
triggerRefValue(this)
}
})
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this.__v_isReadonly = isReadonly
}
get value() {
// 获取原始对象
const self = toRaw(this)
// 收集依赖
trackRefValue(self)
// 如果是 dirty,重新计算
if (self._dirty || !self._cacheable) {
self._dirty = false
// 执行 getter
self._value = self.effect.run()!
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
setter = NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
return cRef as any
}computed 的特性
typescript
// 1. 懒计算
const count = ref(0)
const double = computed(() => {
console.log('computing...')
return count.value * 2
})
// 此时不会执行 getter
console.log('before access')
// 访问时才会计算
console.log(double.value) // 输出: computing... 0
// 2. 缓存
console.log(double.value) // 不会再次计算,直接返回缓存值
// 3. 依赖变化时才重新计算
count.value = 1
console.log(double.value) // 输出: computing... 2
// 4. 可写的 computed
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
console.log(fullName.value) // John Doe
fullName.value = 'Jane Smith'
console.log(firstName.value) // Jane
console.log(lastName.value) // Smith性能优化
1. 使用 shallowRef 避免深层响应式
typescript
// ❌ 不好 - 深层响应式转换有性能开销
const data = ref({
list: [/* 大量数据 */],
nested: {
deep: {
value: 1
}
}
})
// ✅ 好 - 使用 shallowRef
const data = shallowRef({
list: [/* 大量数据 */],
nested: {
deep: {
value: 1
}
}
})
// 需要时整体替换
data.value = newData2. 使用 triggerRef 批量更新
typescript
const state = shallowRef({ count: 0, text: 'hello' })
// 批量修改
state.value.count = 10
state.value.text = 'world'
// 一次性触发更新
triggerRef(state)3. computed 的自动缓存
typescript
// ❌ 不好 - 每次都重新计算
const expensiveValue = () => {
return expensiveOperation()
}
// ✅ 好 - 自动缓存
const expensiveValue = computed(() => {
return expensiveOperation()
})最佳实践
1. 优先使用 ref
typescript
// ✅ 推荐 - 使用 ref
const count = ref(0)
const message = ref('hello')
// ⚠️ 特殊情况 - reactive 更适合
const state = reactive({
user: {
name: 'John',
age: 30
},
settings: {
theme: 'dark'
}
})2. 解构时使用 toRefs
typescript
// ✅ 正确
const state = reactive({ foo: 1, bar: 2 })
const { foo, bar } = toRefs(state)
// ❌ 错误 - 失去响应式
const { foo, bar } = state3. 使用 customRef 实现特殊逻辑
typescript
// ✅ 防抖、节流、双向绑定等
const debouncedText = useDebouncedRef('', 300)
const throttledScroll = useThrottledRef(0, 100)4. computed vs method
typescript
// ✅ 使用 computed - 需要缓存
const filteredList = computed(() => {
return list.value.filter(item => item.active)
})
// ✅ 使用 method - 不需要缓存
function formatDate(date: Date) {
return date.toLocaleDateString()
}ref 与 reactive 的深层关系
为什么 ref 需要 reactive
typescript
// ref 处理对象时的内部流程
const user = ref({
name: 'Alice',
age: 25,
address: {
city: 'Beijing'
}
})
// 实际发生的事情:
// 1. ref 创建 RefImpl 实例
// 2. 对象值被 reactive() 转换为响应式代理
// 3. 嵌套对象也会被递归转换为响应式
user.value.name = 'Bob' // ✅ 响应式
user.value.address.city = 'Shanghai' // ✅ 响应式(深层响应式)
// 这就是为什么说:ref 的底层就是 reactiveref 的完整工作流程
typescript
// 1. 创建 ref
const state = ref({ count: 0 })
// 内部流程:
// RefImpl {
// _rawValue: { count: 0 }, // 原始值
// _value: reactive({ count: 0 }), // 响应式代理值
// dep: Set(), // 依赖集合
// __v_isRef: true // ref 标记
// }
// 2. 访问 .value
console.log(state.value.count)
// 触发:
// - RefImpl 的 get value() getter
// - trackRefValue(this) 收集 ref 的依赖
// - 返回 this._value(reactive 代理对象)
// - 访问 count 触发 reactive 的 get trap
// - track(target, 'count') 收集属性依赖
// 3. 修改值
state.value.count++
// 触发:
// - 访问 count 触发 reactive 的 get trap
// - 修改 count 触发 reactive 的 set trap
// - trigger(target, 'count') 触发属性依赖
// - 不会触发 ref 的 setter(因为 .value 本身没变)
// 4. 替换整个 .value
state.value = { count: 10 }
// 触发:
// - RefImpl 的 set value() setter
// - 新对象被 reactive() 转换
// - triggerRefValue(this) 触发 ref 的依赖统一的响应式系统
typescript
// Vue 3 的响应式系统是统一的
// ref 和 reactive 都基于 Proxy
// ref 是特殊的响应式容器
// - 外层:RefImpl 拦截 .value 的访问
// - 内层:如果值是对象,使用 reactive 代理
// 所以可以说:
// ref = 响应式容器(RefImpl) + reactive(当值为对象时)
// reactive = 直接的 Proxy 代理
// 示例:
const refState = ref({ count: 0 })
const reactiveState = reactive({ count: 0 })
// refState.value 本质上就是一个 reactive 对象
console.log(toRaw(refState.value) === toRaw(reactiveState)) // 结构相同
// 它们都使用相同的依赖收集和触发机制
effect(() => {
console.log(refState.value.count) // 通过 ref 的 getter + reactive 的 getter
console.log(reactiveState.count) // 直接通过 reactive 的 getter
})总结
ref 是 Vue 3 响应式系统的基础:
- ref 的本质 - 将值包装成对象,使用 RefImpl 类管理
- ref 与 reactive 的关系 - ref 的底层就是 reactive,当值为对象时,内部使用 reactive 进行代理
- 依赖收集 - 在 get 时通过 trackRefValue 收集依赖
- 触发更新 - 在 set 时通过 triggerRefValue 触发更新
- 双层响应式 - RefImpl 管理外层(.value),reactive 管理内层(对象属性)
- shallowRef - 只对 .value 响应式,不对内部对象响应式
- customRef - 自定义依赖追踪和触发逻辑
- toRef/toRefs - 保持解构后的响应式连接
- computed - 特殊的 ref,带缓存的计算属性
关键认知
typescript
// ⭐ 重要:理解这个关系
ref(对象) = RefImpl { value: reactive(对象) }
// 所以:
// - ref 需要 .value 访问
// - reactive 直接访问属性
// - 但它们底层都是 Proxy 代理
// - ref 在对象值的情况下,内部依赖 reactive
// 这就是为什么说:
// "ref 的底层就是 reactive,reactive 是对象代理"理解 ref 的实现原理和与 reactive 的关系,能帮助我们:
- 正确使用各种 ref API
- 理解为什么 ref 需要 .value
- 知道何时使用 ref,何时使用 reactive
- 避免响应式丢失
- 优化应用性能
- 实现自定义响应式逻辑