Vue 3 的响应式系统是其核心特性之一,它允许我们以声明式的方式处理数据变化。本文将深入分析 Vue 3 中 reactive.ts 文件的实现原理,帮助你全面理解 Vue 3 响应式系统的内部工作机制。
目录
- Vue 3 Reactivity 系统完全指南:深入理解 reactive.ts
1. 概述
reactive.ts 是 Vue 3 响应式系统的核心模块之一,负责创建和管理响应式对象。它通过 JavaScript 的 Proxy API 实现数据拦截,并结合 WeakMap 缓存机制来优化性能。
该模块提供了多种创建响应式数据的方法,满足不同场景的需求:
reactive: 创建深层响应式对象shallowReactive: 创建浅层响应式对象readonly: 创建深层只读对象shallowReadonly: 创建浅层只读对象
同时提供了一系列工具函数用于检测和操作响应式对象。
2. 核心 API 详解
2.1 reactive
reactive 是最常用的响应式 API,它创建一个深层响应式的代理对象。
export function reactive<T extends object>(target: T): Reactive<T>
特点:
- 深度转换:对象的所有嵌套属性都会被转换为响应式
- 自动解包 ref:如果属性是 ref,则会自动解包但仍保持响应性
- 不会代理非可扩展对象(如冻结对象)
使用示例:
import { reactive } from 'vue'
const state = reactive({
count: 0,
nested: {
foo: 'bar'
}
})
// 访问响应式属性
console.log(state.count) // 0
// 修改响应式属性
state.count++
state.nested.foo = 'baz'
2.2 shallowReactive
shallowReactive 创建一个浅层响应式对象,只有根级别的属性是响应式的。
export function shallowReactive<T extends object>(target: T): ShallowReactive<T>
特点:
- 只有根级别属性是响应式的
- 不会自动解包 ref 值
- 不会对嵌套对象进行递归响应式处理
使用示例:
import { shallowReactive } from 'vue'
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 修改根级属性是响应式的
state.foo++
console.log(state.foo) // 2
// 嵌套对象不是响应式的
state.nested.bar++
// 不会触发响应式更新
2.3 readonly
readonly 创建一个只读代理对象,所有修改操作都会被拦截并警告。
export function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>>
特点:
- 深度只读:所有嵌套属性都是只读的
- 保持响应性:可以追踪变化并触发依赖更新
- 适用于需要防止意外修改的场景
使用示例:
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 读取操作正常工作
console.log(copy.count) // 0
// 修改原始对象会触发依赖更新
original.count++
// 修改副本会发出警告
copy.count++ // warning!
2.4 shallowReadonly
shallowReadonly 创建一个浅层只读对象,只有根级别的属性是只读的。
export function shallowReadonly<T extends object>(target: T): Readonly<T>
特点:
- 只有根级别属性是只读的
- 不会自动解包 ref 值
- 嵌套对象可以被修改
使用示例:
import { shallowReadonly } from 'vue'
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2
}
})
// 修改根级属性会被拦截
state.foo++ // 警告
// 嵌套对象可以被修改
state.nested.bar++ // 正常工作
3. 工具函数解析
3.1 isReactive
检查对象是否是由 reactive 或 shallowReactive 创建的代理。
export function isReactive(value: unknown): boolean
使用示例:
import { reactive, readonly, ref, shallowReactive } from 'vue'
console.log(isReactive(reactive({}))) // true
console.log(isReactive(readonly(reactive({}))) ) // true
console.log(isReactive(ref({}).value)) // true
console.log(isReactive(ref(true))) // false
console.log(isReactive(shallowReactive({}))) // true
3.2 isReadonly
检查对象是否是只读的。
export function isReadonly(value: unknown): boolean
使用示例:
import { reactive, readonly, shallowReadonly } from 'vue'
console.log(isReadonly(readonly({}))) // true
console.log(isReadonly(shallowReadonly({}))) // true
console.log(isReadonly(reactive({}))) // false
3.3 isShallow
检查对象是否是浅层的。
export function isShallow(value: unknown): boolean
3.4 isProxy
检查对象是否是由 reactive、readonly、shallowReactive 或 shallowReadonly 创建的代理。
export function isProxy(value: any): boolean
3.5 toRaw
返回 Vue 创建的代理对象的原始对象。
export function toRaw<T>(observed: T): T
使用示例:
import { reactive, toRaw } from 'vue'
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
3.6 markRaw
标记一个对象,使其永远不会被转换为代理。
export function markRaw<T extends object>(value: T): Raw<T>
使用示例:
import { reactive, markRaw, isReactive } from 'vue'
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 也适用于嵌套对象
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
3.7 toReactive 和 toReadonly
这两个辅助函数用于有条件地创建响应式或只读代理。
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
export const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>
isObject(value) ? readonly(value) : (value as DeepReadonly<T>)
4. 内部实现机制
4.1 createReactiveObject
所有公共 API 都通过 createReactiveObject 函数实现:
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
)
该函数处理了多种边界情况:
- 检查目标是否为对象
- 避免重复代理
- 处理只读和响应式的特殊情况
- 根据对象类型选择合适的处理器
4.2 缓存机制
通过 WeakMap 缓存已创建的代理对象,避免重复创建:
export const reactiveMap: WeakMap<Target, any> = new WeakMap<Target, any>()
export const shallowReactiveMap: WeakMap<Target, any> = new WeakMap<Target, any>()
export const readonlyMap: WeakMap<Target, any> = new WeakMap<Target, any>()
export const shallowReadonlyMap: WeakMap<Target, any> = new WeakMap<Target, any>()
4.3 目标类型检测
通过 TargetType 枚举和相关函数区分不同类型的对象:
enum TargetType {
INVALID = 0,
COMMON = 1,
COLLECTION = 2,
}
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
5. TypeScript 高级特性应用
5.1 泛型与条件类型
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
这个条件类型用于判断是否需要解包嵌套的 refs。
5.2 映射类型
export type DeepReadonly<T> = T extends Builtin
? T
: T extends Map<infer K, infer V>
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
// ... 更多条件分支
: T extends {}
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: Readonly<T>
使用映射类型递归创建深度只读类型。
5.3 infer 关键字
在 DeepReadonly<T> 类型中使用 infer 提取泛型参数:
T extends Map<infer K, infer V>
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
5.4 联合类型与交叉类型
type Primitive = string | number | boolean | bigint | symbol | undefined | null
export type Reactive<T> = UnwrapNestedRefs<T> &
(T extends readonly any[] ? ReactiveMarker : {})
5.5 unique symbol
使用 unique symbol 创建全局唯一的标识符:
declare const ReactiveMarkerSymbol: unique symbol
export interface ReactiveMarker {
[ReactiveMarkerSymbol]?: void
}
5.6 函数重载
export function reactive<T extends object>(target: T): Reactive<T>
export function reactive(target: object)
通过函数重载提供更精确的类型定义。
6. 实际应用示例
import { reactive, readonly, isReactive, toRaw } from 'vue'
// 创建响应式状态
const state = reactive({
users: [],
loading: false,
error: null
})
// 创建只读状态供组件使用
const stateReader = readonly(state)
// 在组件中使用
export default {
data() {
return {
state: stateReader
}
},
methods: {
async loadUsers() {
// 修改原始状态
state.loading = true
try {
const users = await fetchUsers()
state.users = users
} catch (error) {
state.error = error.message
} finally {
state.loading = false
}
}
},
computed: {
userCount() {
// 通过只读代理访问
return this.state.users.length
}
}
}
7. 性能优化策略
- 缓存机制:通过 WeakMap 避免重复创建代理对象
- 类型区分:为不同类型的对象(普通对象、集合)使用不同的处理器
- 跳过标记:通过
markRaw跳过不需要响应式的对象 - 浅层处理:在不需要深度响应式时使用
shallowReactive
8. 总结
Vue 3 的 reactive.ts 文件通过巧妙地结合 Proxy API、WeakMap 缓存和 TypeScript 高级类型系统,实现了强大而灵活的响应式系统。它不仅提供了多种 API 满足不同场景需求,还通过各种优化策略保证了良好的性能。
理解这些实现细节有助于我们:
- 更好地使用 Vue 3 的响应式 API
- 在遇到问题时能够快速定位和解决
- 学习如何在项目中应用类似的模式
通过深入分析这个文件,我们可以看到 Vue 3 在设计上的精妙之处,以及如何通过 TypeScript 的类型系统提供既安全又灵活的 API。