本文档解释了 Vue 响应式系统的核心基元:ref 和响应式对象 的实现。这些基元允许 Vue 高效地跟踪状态变化并相应地更新 DOM。有关基于这些基元构建的计算属性和观察者的信息,请参阅 计算属性和侦听器。
Vue 的响应式系统允许自动跟踪依赖关系并在状态变化时更新 UI。它包含两种主要的响应式状态类型:
来源
Refs 是包装对象,包含一个 .value 属性,使原始值具有响应性。它们是通过一个名为 RefImpl 的类实现的。
访问 Ref 的 .value 属性时,会调用 track() 函数来记录当前 effect 依赖于此 ref。当 .value 被更改时,会调用 trigger() 函数来通知所有依赖于此 ref 的 effects。
来源
Vue 提供了多种 API 函数来创建不同类型的 ref:
| 功能 | 描述 | 来源 |
|---|---|---|
ref(value) | 为任何值类型创建响应式 ref | ref.ts55-61 |
shallowRef(value) | 创建仅跟踪顶层变更的 ref | ref.ts86-96 |
customRef(factory) | 创建具有对跟踪和触发的显式控制的自定义 ref | ref.ts323-325 |
toRef(source, key, defaultValue?) | 创建与源对象的属性同步的 ref | ref.ts430-460 |
toRefs(object) | 将响应式对象转换为 ref 对象 | ref.ts339-348 |
来源
Refs 的核心实现位于 RefImpl 类中。
关于实现的要点:
RefImpl 存储原始值(_rawValue)和可能具有响应性的值(_value)。toReactive() 转换为响应式。.value 的 getter 使用 dep.track() 跟踪依赖。.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 |
来源
响应式系统根据创建的响应式对象类型使用不同的代理处理器。
代理处理器会拦截以下关键操作:
in 操作符),跟踪依赖关系。来源
Vue 使用依赖跟踪系统来了解何时需要更新组件。这通过 Dep 类和 track/trigger 函数来实现。
依赖跟踪系统中的关键实体是:
跟踪过程
track()。trigger()。来源
Ref 和 reactive 对象都提供响应式功能,但它们有不同的用途
| 功能 | Ref | 响应式 |
|---|---|---|
| 原始值 | ✓ | ✗ |
显式 .value 访问 | 必填 | 不适用 |
| 深度响应式 | ✓ | ✓ |
| 浅层版本 | shallowRef() | shallowReactive() |
| 只读版本 | 不适用 | readonly() |
| 在模板中使用 | 自动解包 | 直接访问 |
| 解构 | 保持响应式 | 丢失响应式 |
| TypeScript 使用 | 使用泛型进行显式类型声明 | 类型推断 |
当 ref 和 reactive 对象一起使用时,Vue 会在某些情况下自动处理转换和解包
当 ref 作为 reactive 对象的属性被访问时,它会被自动解包
当一个对象传递给 ref() 时,它在内部会被设置为响应式
unref() 工具函数会解包 ref,如果参数不是 ref,则原样返回其值
来源
Vue 提供了一些工具函数来处理 ref 和 reactive 对象
| 功能 | 描述 | 来源 |
|---|---|---|
isRef(value) | 检查一个值是否为 ref | ref.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 |
来源
响应式系统通过 effect(在响应式状态改变时重新渲染组件)与 Vue 的组件系统集成
当创建组件实例时,Vue 会为组件的渲染函数创建一个响应式 effect。在渲染过程中访问响应式状态时,依赖跟踪系统会记录组件依赖的响应式属性。当这些属性发生变化时,组件会自动重新渲染。
来源
Reactive 对象只会被代理一次。系统使用 WeakMaps 来存储代理实例,并在多次使相同对象响应式时重用它们
来源
Vue 的响应式系统为 Map、Set、WeakMap 和 WeakSet 等集合类型提供了特殊处理程序。这些处理程序会拦截 set、get、add、delete 等方法以及迭代方法,以正确跟踪依赖关系并触发更新。
来源
响应式 API 的浅层版本(shallowRef、shallowReactive 等)只使顶层属性具有响应性。它们适用于:
triggerRef())来源
Ref 和 reactive 对象是 Vue 响应式系统的基础
.value 属性的对象,提供了一种使任何值(包括原始值)具有响应性的方法。它们共同实现了 Vue 的声明式响应式编程模型,即 UI 会随着状态的改变而自动更新。该系统通过代理缓存、批量更新和特殊集合处理等功能进行了性能优化。