Vue3 为什么要发明 ref?—— 值容器抽象的设计逻辑

33 阅读2分钟

一、问题:为什么 reactive 不能解决所有响应式?

Vue3 已经有了 reactive,它可以把对象变成 Proxy。

但问题来了:

const count = 0

基本类型无法被 Proxy 代理。

那怎么办?

Vue3 的答案不是:

修改 JavaScript 语义

而是:

引入一个“值容器抽象”

这就是 ref。


二、ref 的本质:一个“值容器”

ref 不是“响应式变量”。

它是:

一个拥有 getter/setter 的值容器。

当你写:

const count = ref(0)

实际上得到的是:

{
  get value()
  set value()
}

它通过:

  • getter → track
  • setter → trigger

完成响应式。

这是一种非常克制的设计。


三、RefImpl —— 标准 ref 的实现逻辑

核心特征:

  • 内部持有 Dep
  • 使用 .value 作为访问边界
  • 对象自动转 reactive

这里有一个关键设计:

外层 getter/setter + 内层 Proxy

当访问:

  • ref.value → 触发 ref 的依赖
  • ref.value.xxx → 触发 reactive 的依赖

这是一种分层响应式模型


细节:为什么会用到 Object.is?

内部使用:

Object.is(newValue, oldValue)

而不是 ===

原因:

  • 区分 +0 / -0
  • 正确处理 NaN
  • 避免无效 trigger

这是边界值安全设计。


四、CustomRefImpl —— 开放调度权

Vue 并没有把调度逻辑写死。

它允许:

customRef((track, trigger) => {})

这意味着:

响应式系统的调度权被开放给开发者。

典型场景:

  • 防抖
  • 节流
  • 缓存
  • 异步控制

这是“框架可扩展性”的体现。


五、ObjectRefImpl —— 视图,而非拷贝

toRef(obj, 'key')

它没有创建新状态。

它只是:

给对象属性套了一层 value 访问接口。

所以:

  • 它不持有 Dep
  • 它依赖原对象

这是一种“视图抽象”。

非常优雅。


六、GetterRefImpl —— 只读计算视图

toRef(() => x)

特点:

  • 只读
  • 无独立 Dep
  • 依赖来自 getter 内部

这本质上是一个轻量 computed。


七、ref 工厂体系:统一入口抽象

toRef 的真正价值不是转换。

而是:

把多种来源统一为 ref 接口。

无论是:

  • 已是 ref
  • getter
  • 对象属性
  • 普通值

最终都变成:

{ value }

这是接口统一设计。


八、proxyRefs —— 模板自动解包的关键

setup 返回一个对象。

模板访问时:

count

实际上需要:

count.value

proxyRefs 做的事情是:

  • get 时自动 unref
  • set 时自动写回 value

这是语法友好层。 同时是模板自动解包的基础机制。

因为 setup 编译后本质是:

返回一个对象

proxyRefs 负责让模板写法更自然。


九、工程启发:ref 设计的三层抽象

从架构角度看,ref 模块体现了:

  1. 值容器抽象
  2. 接口统一
  3. 调度可扩展

它不是“响应式补丁”。

而是:

把 JS 的值语义提升到可追踪层。