一文彻底搞懂 Vue3 中 ref 的源码实现(含详细注释)

在 Vue 3 的响应式系统中,ref 是最基础也是最核心的响应式单元。

几乎所有的 reactivecomputedwatchEffect 等能力都与 ref 紧密相关。

本文我们从源码入手,逐行讲解 Vue3 的 ref 实现原理。


一、ref 是什么

ref 的本质是一个带有 .value 属性的响应式对象。

.value 改变时,依赖它的副作用(effect)就会重新执行。

简单示例:

javascript 复制代码
import { ref, effect } from 'vue'

const count = ref(0)
effect(() => {
  console.log(count.value)
})
count.value++ // 触发 effect,打印新值

我们来看看 Vue3 中它是怎么实现的。


二、源码结构总览

文件路径:

bash 复制代码
packages/reactivity/src/ref.ts

主要包含以下部分:

  1. Ref 接口定义
  2. isRef / ref / shallowRef 方法
  3. RefImpl 类(核心实现)
  4. triggerRefunreftoValue 等辅助函数
  5. toReftoRefsproxyRefs 等工具
  6. 一系列类型定义(UnwrapRef、ShallowUnwrapRef 等)

三、Ref 接口定义

typescript 复制代码
export interface Ref<T = any, S = T> {
  get value(): T
  set value(_: S)
  [RefSymbol]: true // 用私有 Symbol 区分类型(避免出现在自动提示中)
}

👉 这里定义了一个具有 value 访问器的响应式引用类型。

所有的 ref() 调用最终都会生成一个实现了该接口的对象。


四、isRef 判断函数

arduino 复制代码
export function isRef(r: any): r is Ref {
  return r ? r[ReactiveFlags.IS_REF] === true : false
}

逻辑非常简单:

  • 判断对象上是否存在 ReactiveFlags.IS_REF(即 __v_isRef)标志位。
  • 这是 Vue 内部区分"是否为 ref 实例"的方式。

五、ref() 函数入口

javascript 复制代码
export function ref(value?: unknown) {
  return createRef(value, false)
}

ref() 就是对 createRef 的封装。

第二个参数 false 表示不是浅层 ref(即内部值也会被响应化)。


六、shallowRef()

javascript 复制代码
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}

与普通 ref 的唯一区别是:

  • 它不会递归地把对象内部变为响应式。
  • 只有 .value 层级被追踪。

示例:

ini 复制代码
const state = shallowRef({ count: 1 })
state.value.count++ // 不触发更新
state.value = { count: 2 } // 触发更新

七、createRef 函数

php 复制代码
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

功能:

  1. 若传入本身就是 ref,则直接返回;
  2. 否则创建新的 RefImpl 实例。

八、RefImpl 核心类

这是整个 ref 响应式系统的核心实现。

typescript 复制代码
class RefImpl<T = any> {
  _value: T
  private _rawValue: T
  dep: Dep = new Dep()
  public readonly [ReactiveFlags.IS_REF] = true
  public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false

  constructor(value: T, isShallow: boolean) {
    this._rawValue = isShallow ? value : toRaw(value)
    this._value = isShallow ? value : toReactive(value)
    this[ReactiveFlags.IS_SHALLOW] = isShallow
  }

这里:

  • _rawValue 存原始值;
  • _value 存转换后的响应式值;
  • dep 是依赖收集容器;
  • ReactiveFlags.IS_REF 标志这是一个 ref 对象。

getter 部分

kotlin 复制代码
get value() {
  if (__DEV__) {
    this.dep.track({
      target: this,
      type: TrackOpTypes.GET,
      key: 'value',
    })
  } else {
    this.dep.track()
  }
  return this._value
}

当访问 .value 时:

  • 触发依赖收集(track
  • 将当前 effect 注册到 dep
  • 返回内部值

setter 部分

kotlin 复制代码
set value(newValue) {
  const oldValue = this._rawValue
  const useDirectValue =
    this[ReactiveFlags.IS_SHALLOW] ||
    isShallow(newValue) ||
    isReadonly(newValue)

  newValue = useDirectValue ? newValue : toRaw(newValue)

  if (hasChanged(newValue, oldValue)) {
    this._rawValue = newValue
    this._value = useDirectValue ? newValue : toReactive(newValue)
    if (__DEV__) {
      this.dep.trigger({
        target: this,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue,
        oldValue,
      })
    } else {
      this.dep.trigger()
    }
  }
}

主要逻辑:

  1. 判断是否需要递归响应化;
  2. 若值发生变化(hasChanged);
  3. 更新 _value_rawValue
  4. 通知所有依赖(trigger)。

九、triggerRef

csharp 复制代码
export function triggerRef(ref: Ref): void {
  if ((ref as any).dep) {
    (ref as any).dep.trigger()
  }
}

这个函数用于手动触发 shallowRef 的依赖更新。

例如:

ini 复制代码
const shallow = shallowRef({ greet: 'hi' })
shallow.value.greet = 'hello'
triggerRef(shallow) // 手动触发更新

十、unref 与 toValue

csharp 复制代码
export function unref<T>(ref: MaybeRef<T>): T {
  return isRef(ref) ? ref.value : ref
}

unref 是个语法糖:

  • 如果参数是 ref,则返回 .value
  • 否则直接返回原值

toValueunref 的扩展:

  • 如果传入函数,会自动调用。
bash 复制代码
export function toValue<T>(source: MaybeRefOrGetter<T>): T {
  return isFunction(source) ? source() : unref(source)
}

十一、proxyRefs

typescript 复制代码
export function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

作用:创建一个代理对象,访问时自动"解包 ref"。

例如:

sql 复制代码
const user = proxyRefs({ name: ref('Tom') })
console.log(user.name) // 直接得到 'Tom',不用 .value

十二、toRefs 与 toRef

toRefs

把一个响应式对象的每个属性都转为 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] = propertyToRef(object, key)
  }
  return ret
}

toRef

针对单个属性创建同步 ref:

vbnet 复制代码
export function toRef(object, key, defaultValue?) {
  return propertyToRef(object, key, defaultValue)
}

示例:

ini 复制代码
const state = reactive({ foo: 1 })
const fooRef = toRef(state, 'foo')
fooRef.value++  // state.foo 也随之变化

十三、Ref 类型展开(UnwrapRef)

Vue 中大量使用 UnwrapRef<T> 来推导类型。

swift 复制代码
export type UnwrapRef<T> =
  T extends ShallowRef<infer V> ? V
  : T extends Ref<infer V> ? UnwrapRefSimple<V>
  : UnwrapRefSimple<T>

作用是把 Ref<number> 展开为 number,从而在模板或逻辑中直接使用。


十四、总结

ref 是 Vue3 响应式系统的最小原子单元。

它的实现基于以下机制:

  1. 每个 ref 对象内部维护一个依赖集合(Dep)
  2. 访问 .value 时进行依赖收集(track)
  3. 修改 .value 时触发依赖更新(trigger)
  4. 支持 shallow、custom、toRef、toRefs 等扩展用法
  5. 与 reactive 一起构成完整的响应式系统

一句话总结:

ref 是响应式系统中的"最小反应单元",为单值状态提供自动追踪能力。


本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。

相关推荐
鹏多多4 小时前
react-konva实战指南:Canvas高性能+易维护的组件化图形开发实现教程
前端·javascript·react.js
excel4 小时前
一文彻底搞懂 Vue 中的 key(含 Vue2 / Vue3 对比)
前端
冰暮流星4 小时前
css新增盒子属性——尺寸调节
前端·css
程序员爱钓鱼4 小时前
Python编程实战 - 函数与模块化编程 - 函数的定义与调用
前端·后端·python
欧阳码农4 小时前
使用AI生成的页面总是被一眼认出来怎么办?1分钟给你解决
前端·后端
IT_陈寒4 小时前
7个鲜为人知的JavaScript性能优化技巧,让你的应用提速50%!
前端·人工智能·后端
艾小码5 小时前
前端别再乱存数据了!这3种存储方案让你的应用快如闪电
前端·javascript
黄毛火烧雪下5 小时前
HTML 的底层原理
前端·html
Moment5 小时前
面经分享——字节前端一面
前端·javascript·面试