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);
}

技术要点解析

  1. 自动对象转换
    ref 接受对象时,内部自动调用 reactive

    const objRef = ref({ count: 0 }) 
    objRef.value.count++ // 触发更新(因内部是 reactive)
    
  2. 值变更比对优化
    使用 Object.is() 处理 NaN+0/-0 的特殊情况:

    function hasChanged(newVal, oldVal) {
      return !Object.is(newVal, oldVal);
    }
    
  3. 模板自动解包
    在模板中直接使用 ref 会省略 .value

    <template>
      <!-- 直接写 count 而非 count.value -->
      <div>{{ count }}</div>
    </template>
    <script setup>
    const count = ref(0) // 编译时自动解包
    </script>
    
  4. 响应式解包机制
    ref 嵌套在 reactive 对象中时,自动解包 .value

    const r = ref(0);
    const obj = reactive({ r });
    console.log(obj.r); // 输出 0,而非 ref 对象
    

边界处理与工具函数

  1. unref 工具函数:解包 ref 或返回原值

    function unref(ref) {
      return isRef(ref) ? ref.value : ref;
    }
    
  2. 处理只读 ref (readonly(ref(0))):
    通过 shallowReadonly 阻断 .value 修改:

    function readonlyRef(ref) {
      return Object.freeze({ get value() { return ref.value } });
    }
    
  3. 自定义 Ref (customRef):
    手动控制依赖跟踪与触发逻辑:

    function customRef(factory) {
      class CustomRefImpl extends RefImpl {
        constructor(getter, setter) {
          super(undefined);
          // 用户自定义 track/trigger 逻辑
        }
      }
      return new CustomRefImpl(factory());
    }
    

性能优化

  1. 惰性依赖收集
    仅当在 effect 中访问 .value 时才收集依赖,避免不必要的内存开销。

  2. 轻量级对象包装
    RefImpl 类比 reactiveProxy 更轻量,尤其适合基础类型。

  3. 复用依赖存储结构
    共用全局的 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 的基础工具之一。

posted @   木燃不歇  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示