菜单

Refs 和响应式对象

相关源文件

本文档解释了 Vue 响应式系统的核心基元:ref 和响应式对象 的实现。这些基元允许 Vue 高效地跟踪状态变化并相应地更新 DOM。有关基于这些基元构建的计算属性和观察者的信息,请参阅 计算属性和侦听器

Vue 响应式概览

Vue 的响应式系统允许自动跟踪依赖关系并在状态变化时更新 UI。它包含两种主要的响应式状态类型:

  1. Refs - 包装值的引用对象,适用于原始类型和对象
  2. 响应式对象 - 通过代理(proxy)实现的 JavaScript 对象

来源

Ref 实现

Refs 是包装对象,包含一个 .value 属性,使原始值具有响应性。它们是通过一个名为 RefImpl 的类实现的。

访问 Ref 的 .value 属性时,会调用 track() 函数来记录当前 effect 依赖于此 ref。当 .value 被更改时,会调用 trigger() 函数来通知所有依赖于此 ref 的 effects。

来源

创建 Refs

Vue 提供了多种 API 函数来创建不同类型的 ref:

功能描述来源
ref(value)为任何值类型创建响应式 refref.ts55-61
shallowRef(value)创建仅跟踪顶层变更的 refref.ts86-96
customRef(factory)创建具有对跟踪和触发的显式控制的自定义 refref.ts323-325
toRef(source, key, defaultValue?)创建与源对象的属性同步的 refref.ts430-460
toRefs(object)将响应式对象转换为 ref 对象ref.ts339-348

来源

Ref 内部实现

Refs 的核心实现位于 RefImpl 类中。

关于实现的要点:

  1. RefImpl 存储原始值(_rawValue)和可能具有响应性的值(_value)。
  2. 当使用非原始值创建时,该值使用 toReactive() 转换为响应式。
  3. 通过 .value 的 getter 使用 dep.track() 跟踪依赖。
  4. 通过 .value 的 setter 使用 dep.trigger() 触发更新,仅当值发生变化时。

来源

响应式对象

响应式对象使用 JavaScript Proxies 来拦截属性访问和修改。它们默认提供深度响应性。

创建响应式对象

Vue 提供了多种 API 函数来创建不同类型的响应式对象:

功能描述来源
reactive(obj)创建一个深度响应式对象reactive.ts91-104
shallowReactive(obj)创建仅具有顶层响应性(top-level reactivity)的对象reactive.ts140-150
readonly(obj)创建一个深度只读对象reactive.ts205-215
shallowReadonly(obj)创建一个仅具有顶层保护(top-level protection)的只读对象reactive.ts247-255

来源

Proxy Handlers (代理处理器)

响应式系统根据创建的响应式对象类型使用不同的代理处理器。

代理处理器会拦截以下关键操作:

  1. get: 访问属性时,跟踪依赖关系并返回值。
  2. set: 修改属性时,如果值已更改,则触发更新。
  3. deleteProperty: 删除属性时,触发更新。
  4. has: 检查属性是否存在时(使用 in 操作符),跟踪依赖关系。
  5. ownKeys: 迭代属性时,跟踪对象结构的依赖关系。

来源

依赖跟踪

Vue 使用依赖跟踪系统来了解何时需要更新组件。这通过 Dep 类和 track/trigger 函数来实现。

依赖跟踪系统中的关键实体是:

  1. targetMap: 一个 WeakMap,将对象映射到其依赖项映射。
  2. Dep: 一个类,用于跟踪特定响应式属性的订阅者(effects)。
  3. Link: 一个类,表示 Dep 和订阅者之间的连接。
  4. Effect: 一个类,表示一个副作用函数,该函数在依赖项更改时应重新运行。

跟踪过程

  1. 当访问响应式属性时,会调用 track()
  2. 它会查找或创建相应的 Dep。
  3. 它会在 Dep 和当前正在运行的 effect 之间创建一个 Link。
  4. 当属性被修改时,会调用 trigger()
  5. 它会查找依赖于该属性的所有 effects 并安排它们运行。

来源

Ref 与 Reactive:何时使用它们

Ref 和 reactive 对象都提供响应式功能,但它们有不同的用途

功能Ref响应式
原始值
显式 .value 访问必填不适用
深度响应式
浅层版本shallowRef()shallowReactive()
只读版本不适用readonly()
在模板中使用自动解包直接访问
解构保持响应式丢失响应式
TypeScript 使用使用泛型进行显式类型声明类型推断

Ref-Reactive 转换和解包

当 ref 和 reactive 对象一起使用时,Vue 会在某些情况下自动处理转换和解包

  1. 当 ref 作为 reactive 对象的属性被访问时,它会被自动解包

  2. 当一个对象传递给 ref() 时,它在内部会被设置为响应式

  3. unref() 工具函数会解包 ref,如果参数不是 ref,则原样返回其值

来源

常用工具函数

Vue 提供了一些工具函数来处理 ref 和 reactive 对象

功能描述来源
isRef(value)检查一个值是否为 refref.ts43-46
isReactive(value)检查一个对象是否为响应式reactive.ts318-323
isReadonly(value)检查一个对象是否为只读reactive.ts336-338
isProxy(value)检查一个对象是否为响应式代理或只读代理reactive.ts351-353
unref(value)如果参数是 ref,则返回其内部值,否则返回参数本身ref.ts226-228
toRaw(observed)返回 Vue 创建的代理的原始、未经修改的对象reactive.ts378-381
markRaw(value)将对象标记为永远不会被转换为代理reactive.ts407-412
toValue(source)规范化值/ref/getter为值ref.ts246-248

来源

与 Vue 组件系统的集成

响应式系统通过 effect(在响应式状态改变时重新渲染组件)与 Vue 的组件系统集成

当创建组件实例时,Vue 会为组件的渲染函数创建一个响应式 effect。在渲染过程中访问响应式状态时,依赖跟踪系统会记录组件依赖的响应式属性。当这些属性发生变化时,组件会自动重新渲染。

来源

高级主题

对象代理和 WeakMap 存储

Reactive 对象只会被代理一次。系统使用 WeakMaps 来存储代理实例,并在多次使相同对象响应式时重用它们

来源

集合类型 (Map, Set, WeakMap, WeakSet)

Vue 的响应式系统为 Map、Set、WeakMap 和 WeakSet 等集合类型提供了特殊处理程序。这些处理程序会拦截 setgetadddelete 等方法以及迭代方法,以正确跟踪依赖关系并触发更新。

来源

浅层版本及其用例

响应式 API 的浅层版本(shallowRefshallowReactive 等)只使顶层属性具有响应性。它们适用于:

  1. 大型数据结构,深层响应式可能开销较大
  2. 外部库集成,希望保留原始对象
  3. 需要手动控制嵌套更新何时发生的情况(triggerRef()

来源

结论

Ref 和 reactive 对象是 Vue 响应式系统的基础

  • Refs 通过包装一个具有 .value 属性的对象,提供了一种使任何值(包括原始值)具有响应性的方法。
  • Reactive 对象 使用 JavaScript Proxies 来深度跟踪属性访问和修改。

它们共同实现了 Vue 的声明式响应式编程模型,即 UI 会随着状态的改变而自动更新。该系统通过代理缓存、批量更新和特殊集合处理等功能进行了性能优化。