Vue 3 reactive.ts 源码理解

187 阅读7分钟

Vue 3 的响应式系统是其核心特性之一,它允许我们以声明式的方式处理数据变化。本文将深入分析 Vue 3 中 reactive.ts 文件的实现原理,帮助你全面理解 Vue 3 响应式系统的内部工作机制。

目录

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

检查对象是否是由 reactiveshallowReactive 创建的代理。

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>,
)

该函数处理了多种边界情况:

  1. 检查目标是否为对象
  2. 避免重复代理
  3. 处理只读和响应式的特殊情况
  4. 根据对象类型选择合适的处理器

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. 性能优化策略

  1. 缓存机制:通过 WeakMap 避免重复创建代理对象
  2. 类型区分:为不同类型的对象(普通对象、集合)使用不同的处理器
  3. 跳过标记:通过 markRaw 跳过不需要响应式的对象
  4. 浅层处理:在不需要深度响应式时使用 shallowReactive

8. 总结

Vue 3 的 reactive.ts 文件通过巧妙地结合 Proxy API、WeakMap 缓存和 TypeScript 高级类型系统,实现了强大而灵活的响应式系统。它不仅提供了多种 API 满足不同场景需求,还通过各种优化策略保证了良好的性能。

理解这些实现细节有助于我们:

  1. 更好地使用 Vue 3 的响应式 API
  2. 在遇到问题时能够快速定位和解决
  3. 学习如何在项目中应用类似的模式

通过深入分析这个文件,我们可以看到 Vue 3 在设计上的精妙之处,以及如何通过 TypeScript 的类型系统提供既安全又灵活的 API。