Vue 3 reactive.ts 源码理解

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

目录

  • [Vue 3 Reactivity 系统完全指南:深入理解 reactive.ts](#Vue 3 Reactivity 系统完全指南:深入理解 reactive.ts "#vue-3-reactivity-%E7%B3%BB%E7%BB%9F%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3-reactivets")
    • 目录
    • [1. 概述](#1. 概述 "#1-%E6%A6%82%E8%BF%B0")
    • [2. 核心 API 详解](#2. 核心 API 详解 "#2-%E6%A0%B8%E5%BF%83-api-%E8%AF%A6%E8%A7%A3")
      • [2.1 reactive](#2.1 reactive "#21-reactive")
      • [2.2 shallowReactive](#2.2 shallowReactive "#22-shallowreactive")
      • [2.3 readonly](#2.3 readonly "#23-readonly")
      • [2.4 shallowReadonly](#2.4 shallowReadonly "#24-shallowreadonly")
    • [3. 工具函数解析](#3. 工具函数解析 "#3-%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0%E8%A7%A3%E6%9E%90")
      • [3.1 isReactive](#3.1 isReactive "#31-isreactive")
      • [3.2 isReadonly](#3.2 isReadonly "#32-isreadonly")
      • [3.3 isShallow](#3.3 isShallow "#33-isshallow")
      • [3.4 isProxy](#3.4 isProxy "#34-isproxy")
      • [3.5 toRaw](#3.5 toRaw "#35-toraw")
      • [3.6 markRaw](#3.6 markRaw "#36-markraw")
      • [3.7 toReactive 和 toReadonly](#3.7 toReactive 和 toReadonly "#37-toreactive-%E5%92%8C-toreadonly")
    • [4. 内部实现机制](#4. 内部实现机制 "#4-%E5%86%85%E9%83%A8%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6")
      • [4.1 createReactiveObject](#4.1 createReactiveObject "#41-createreactiveobject")
      • [4.2 缓存机制](#4.2 缓存机制 "#42-%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6")
      • [4.3 目标类型检测](#4.3 目标类型检测 "#43-%E7%9B%AE%E6%A0%87%E7%B1%BB%E5%9E%8B%E6%A3%80%E6%B5%8B")
    • [5. TypeScript 高级特性应用](#5. TypeScript 高级特性应用 "#5-typescript-%E9%AB%98%E7%BA%A7%E7%89%B9%E6%80%A7%E5%BA%94%E7%94%A8")
      • [5.1 泛型与条件类型](#5.1 泛型与条件类型 "#51-%E6%B3%9B%E5%9E%8B%E4%B8%8E%E6%9D%A1%E4%BB%B6%E7%B1%BB%E5%9E%8B")
      • [5.2 映射类型](#5.2 映射类型 "#52-%E6%98%A0%E5%B0%84%E7%B1%BB%E5%9E%8B")
      • [5.3 infer 关键字](#5.3 infer 关键字 "#53-infer-%E5%85%B3%E9%94%AE%E5%AD%97")
      • [5.4 联合类型与交叉类型](#5.4 联合类型与交叉类型 "#54-%E8%81%94%E5%90%88%E7%B1%BB%E5%9E%8B%E4%B8%8E%E4%BA%A4%E5%8F%89%E7%B1%BB%E5%9E%8B")
      • [5.5 unique symbol](#5.5 unique symbol "#55-unique-symbol")
      • [5.6 函数重载](#5.6 函数重载 "#56-%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD")
    • [6. 实际应用示例](#6. 实际应用示例 "#6-%E5%AE%9E%E9%99%85%E5%BA%94%E7%94%A8%E7%A4%BA%E4%BE%8B")
    • [7. 性能优化策略](#7. 性能优化策略 "#7-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E7%AD%96%E7%95%A5")
    • [8. 总结](#8. 总结 "#8-%E6%80%BB%E7%BB%93")

1. 概述

reactive.ts 是 Vue 3 响应式系统的核心模块之一,负责创建和管理响应式对象。它通过 JavaScript 的 Proxy API 实现数据拦截,并结合 WeakMap 缓存机制来优化性能。

该模块提供了多种创建响应式数据的方法,满足不同场景的需求:

  • reactive: 创建深层响应式对象
  • shallowReactive: 创建浅层响应式对象
  • readonly: 创建深层只读对象
  • shallowReadonly: 创建浅层只读对象

同时提供了一系列工具函数用于检测和操作响应式对象。

2. 核心 API 详解

2.1 reactive

reactive 是最常用的响应式 API,它创建一个深层响应式的代理对象。

typescript 复制代码
export function reactive<T extends object>(target: T): Reactive<T>

特点:

  • 深度转换:对象的所有嵌套属性都会被转换为响应式
  • 自动解包 ref:如果属性是 ref,则会自动解包但仍保持响应性
  • 不会代理非可扩展对象(如冻结对象)

使用示例:

javascript 复制代码
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 创建一个浅层响应式对象,只有根级别的属性是响应式的。

typescript 复制代码
export function shallowReactive<T extends object>(target: T): ShallowReactive<T>

特点:

  • 只有根级别属性是响应式的
  • 不会自动解包 ref 值
  • 不会对嵌套对象进行递归响应式处理

使用示例:

javascript 复制代码
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 创建一个只读代理对象,所有修改操作都会被拦截并警告。

typescript 复制代码
export function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>>

特点:

  • 深度只读:所有嵌套属性都是只读的
  • 保持响应性:可以追踪变化并触发依赖更新
  • 适用于需要防止意外修改的场景

使用示例:

javascript 复制代码
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 创建一个浅层只读对象,只有根级别的属性是只读的。

typescript 复制代码
export function shallowReadonly<T extends object>(target: T): Readonly<T>

特点:

  • 只有根级别属性是只读的
  • 不会自动解包 ref 值
  • 嵌套对象可以被修改

使用示例:

javascript 复制代码
import { shallowReadonly } from 'vue'

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 修改根级属性会被拦截
state.foo++ // 警告

// 嵌套对象可以被修改
state.nested.bar++ // 正常工作

3. 工具函数解析

3.1 isReactive

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

typescript 复制代码
export function isReactive(value: unknown): boolean

使用示例:

javascript 复制代码
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

检查对象是否是只读的。

typescript 复制代码
export function isReadonly(value: unknown): boolean

使用示例:

javascript 复制代码
import { reactive, readonly, shallowReadonly } from 'vue'

console.log(isReadonly(readonly({})))         // true
console.log(isReadonly(shallowReadonly({})))  // true
console.log(isReadonly(reactive({})))         // false

3.3 isShallow

检查对象是否是浅层的。

typescript 复制代码
export function isShallow(value: unknown): boolean

3.4 isProxy

检查对象是否是由 reactive、readonly、shallowReactive 或 shallowReadonly 创建的代理。

typescript 复制代码
export function isProxy(value: any): boolean

3.5 toRaw

返回 Vue 创建的代理对象的原始对象。

typescript 复制代码
export function toRaw<T>(observed: T): T

使用示例:

javascript 复制代码
import { reactive, toRaw } from 'vue'

const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true

3.6 markRaw

标记一个对象,使其永远不会被转换为代理。

typescript 复制代码
export function markRaw<T extends object>(value: T): Raw<T>

使用示例:

javascript 复制代码
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

这两个辅助函数用于有条件地创建响应式或只读代理。

typescript 复制代码
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 函数实现:

typescript 复制代码
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
)

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

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

4.2 缓存机制

通过 WeakMap 缓存已创建的代理对象,避免重复创建:

typescript 复制代码
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 枚举和相关函数区分不同类型的对象:

typescript 复制代码
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 泛型与条件类型

typescript 复制代码
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>

这个条件类型用于判断是否需要解包嵌套的 refs。

5.2 映射类型

typescript 复制代码
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 提取泛型参数:

typescript 复制代码
T extends Map<infer K, infer V>
  ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>

5.4 联合类型与交叉类型

typescript 复制代码
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 创建全局唯一的标识符:

typescript 复制代码
declare const ReactiveMarkerSymbol: unique symbol

export interface ReactiveMarker {
  [ReactiveMarkerSymbol]?: void
}

5.6 函数重载

typescript 复制代码
export function reactive<T extends object>(target: T): Reactive<T>
export function reactive(target: object)

通过函数重载提供更精确的类型定义。

6. 实际应用示例

javascript 复制代码
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。

相关推荐
wycode21 分钟前
Vue2实践(3)之用component做一个动态表单(二)
前端·javascript·vue.js
wycode1 小时前
Vue2实践(2)之用component做一个动态表单(一)
前端·javascript·vue.js
第七种黄昏1 小时前
Vue3 中的 ref、模板引用和 defineExpose 详解
前端·javascript·vue.js
pepedd8642 小时前
还在开发vue2老项目吗?本文带你梳理vue版本区别
前端·vue.js·trae
前端缘梦3 小时前
深入理解 Vue 中的虚拟 DOM:原理与实战价值
前端·vue.js·面试
HWL56793 小时前
pnpm(Performant npm)的安装
前端·vue.js·npm·node.js
柯南95274 小时前
Vue 3 Ref 源码解析
vue.js
小高0074 小时前
面试官:npm run build 到底干了什么?从 package.json 到 dist 的 7 步拆解
前端·javascript·vue.js
JayceM5 小时前
Vue中v-show与v-if的区别
前端·javascript·vue.js