Reactive API源码解读

18 阅读10分钟

reactive API

reactive API 用于创建一个响应式对象,允许 Vue 在对象的值发生变化时自动更新视图。

基本使用

import { reactive } from 'vue';

const state = reactive({
  count: 0,
  nested: {
    message: 'Hello World',
  },
});

 深度响应

reactive 可以处理深层嵌套对象:

const state = reactive({
  user: {
    name: 'Alice',
    address: {
      city: 'Wonderland',
    },
  },
});

// 更新嵌套属性
state.user.address.city = 'New Wonderland';

// 输出更新后的值
console.log(state.user.address.city); // 输出: New Wonderland

即使是嵌套对象的属性,使用 reactive 也能自动跟踪并响应于变化。

注意事项

  • 对象必须是可以扩展的(即未被冻结的对象)。
  • 对于不可扩展的对象(例如,使用 Object.freeze() 冻结的对象),reactive 不会对其进行响应式处理。
  • 使用 reactive 转换普通对象时,该对象及其所有嵌套对象都将变为响应式。

源码位置

以下这段代码主要包含了 Vue 3 响应式系统的一些关键功能,特别是在处理原始对象(raw object)、响应式代理(reactive proxy)和只读代理(readonly proxy)方面。

Vue3源码目录:packages\reactivity\src\reactive.ts

Reactive API 主要部分解析:

  1. 判断只读

    • 如果传入的目标对象已经是只读 (readonly),则直接返回该对象,这样可以避免对其进行再次处理。
  2. 创建响应式对象

    • 如果目标对象不是只读,将其传递给 createReactiveObject 函数,这个函数将负责创建并返回一个响应式的代理对象。
  3. 参数说明

    • target:需要被转为响应式的目标对象。
    • false:表示这个对象是可变的不是只读。
    • mutableHandlers 和 mutableCollectionHandlers 是处理响应式行为的处理器,分别用于常规对象和集合对象。

createReactiveObject 函数

createReactiveObject 函数是整个响应式处理的核心逻辑之一,负责真正执行对象的转化。

createReactiveObject 函数主要部分解析:

  1. 检查目标对象的有效性

    • 如果目标不是一个对象,返回原值,同时在开发环境下警告开发者。
  2. 代理重复检测

    • 检查目标对象是否已经有对应的代理,避免多次代理同一对象。
  3. 对象类型检查

    • 使用 getTargetType 方法获取目标对象的类型,确保只处理支持的对象类型(如普通对象、集合)。
  4. 创建响应式代理

    • 使用 JavaScript 的 Proxy 来创建目标对象的代理,并根据目标类型选择合适的处理器。
  5. 存储代理关系

    • 将代理对象存入 WeakMap 中,以便后续能快速查找,避免重复创建。
// 导入共享工具函数
import { def, hasOwn, isObject, toRawType } from '@vue/shared';
// 导入基于不同响应需求的处理器
import {
  mutableHandlers,
  readonlyHandlers,
  shallowReactiveHandlers,
  shallowReadonlyHandlers,
} from './baseHandlers';
import {
  mutableCollectionHandlers,
  readonlyCollectionHandlers,
  shallowCollectionHandlers,
  shallowReadonlyCollectionHandlers,
} from './collectionHandlers';
import type { RawSymbol, Ref, UnwrapRefSimple } from './ref';
import { ReactiveFlags } from './constants';
import { warn } from './warning';

// 定义目标类型接口
export interface Target {
  [ReactiveFlags.SKIP]?: boolean;     // 用于跳过响应式处理的标记
  [ReactiveFlags.IS_REACTIVE]?: boolean; // 判断对象是否为响应式的标记
  [ReactiveFlags.IS_READONLY]?: boolean; // 判断对象是否为只读的标记
  [ReactiveFlags.IS_SHALLOW]?: boolean; // 判断对象是否为浅层响应式的标记
  [ReactiveFlags.RAW]?: any;            // 存储原始对象的标记
}

// 定义各类响应式对象的 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>();

// 列举各种目标类型
enum TargetType {
  INVALID = 0,   // 无效类型
  COMMON = 1,    // 常规对象类型
  COLLECTION = 2, // 集合类型(如 Map 和 Set)
}

// 定义目标类型映射函数
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; // 无效类型
  }
}

// 获取目标类型,用于判断是否需要响应式处理
function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID // 如果对象不可扩展或被标记为跳过,返回无效类型
    : targetTypeMap(toRawType(value)); // 否则返回相应的目标类型
}

// 只展开嵌套 ref 的类型
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>;

declare const ReactiveMarkerSymbol: unique symbol; // 用于标记反应式对象的唯一符号

export interface ReactiveMarker {
  [ReactiveMarkerSymbol]?: void; // 反应式标记接口
}

// 定义响应式类型
export type Reactive<T> = UnwrapNestedRefs<T> &
  (T extends readonly any[] ? ReactiveMarker : {});

// 创建反应式对象的函数
/**
 * 返回一个对象的响应式代理。
 *
 * 响应式转换是“深层”的:它影响所有嵌套属性。
 * 一个响应式对象还会深入展开任何属性为 ref 的属性,同时保持响应性。
 *
 * @param target - 源对象。
 * @see {@link https://vuejs.org/api/reactivity-core.html#reactive}
 */
export function reactive<T extends object>(target: T): Reactive<T>;
// 定义相应的实现函数
export function reactive(target: object) {
  // 如果尝试观察只读代理,则返回只读版本。
  if (isReadonly(target)) {
    return target; // 如果是只读对象,直接返回原对象
  }
  // 创建反应式对象
  return createReactiveObject(
    target,
    false, // 不是只读的
    mutableHandlers, // 基本处理器
    mutableCollectionHandlers, // 集合处理器
    reactiveMap // 存储代理对象的 WeakMap
  );
}

// 定义浅层响应式标记符号
export declare const ShallowReactiveMarker: unique symbol; 

// 定义浅层响应式类型
export type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true; };

// 创建浅层响应式对象的函数
/**
 * {@link reactive()} 的浅层版本。
 *
 * 与 {@link reactive()} 不同,没有深转换:只有根级属性是响应式的。
 * 属性值以原样存储和暴露-这也意味着具有 ref 值的属性不会被自动展开。
 *
 * @param target - 源对象。
 * @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowreactive}
 */
export function shallowReactive<T extends object>(target: T): ShallowReactive<T> {
  return createReactiveObject(
    target,
    false, // 不是只读的
    shallowReactiveHandlers, // 浅层处理器
    shallowCollectionHandlers, // 浅层集合处理器
    shallowReactiveMap // 存储代理对象的 WeakMap
  );
}

// 定义基本类型的集合
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
export type Builtin = Primitive | Function | Date | Error | RegExp;

// 定义深度只读类型
export type DeepReadonly<T> = T extends Builtin
  ? T
  : T extends Map<infer K, infer V>
    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
    : T extends ReadonlyMap<infer K, infer V>
      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
      : T extends WeakMap<infer K, infer V>
        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
        : T extends Set<infer U>
          ? ReadonlySet<DeepReadonly<U>>
          : T extends ReadonlySet<infer U>
            ? ReadonlySet<DeepReadonly<U>>
            : T extends WeakSet<infer U>
              ? WeakSet<DeepReadonly<U>>
              : T extends Promise<infer U>
                ? Promise<DeepReadonly<U>>
                : T extends Ref<infer U, unknown>
                  ? Readonly<Ref<DeepReadonly<U>>>
                  : T extends {}
                    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
                    : Readonly<T>;

// 创建只读对象的函数
/**
 * 将对象(响应式对象或普通对象)或一个 ref 返回只读代理。
 *
 * 只读代理是深度的:访问的任何嵌套属性也是只读的。
 * 它还具有与 {@link reactive()} 相同的展开 ref 的行为,
 * 不同的是,展开的值也将被标记为只读。
 *
 * @param target - 源对象。
 * @see {@link https://vuejs.org/api/reactivity-core.html#readonly}
 */
export function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {
  return createReactiveObject(
    target,
    true, // 只读的
    readonlyHandlers, // 只读处理器
    readonlyCollectionHandlers, // 只读集合处理器
    readonlyMap // 存储代理对象的 WeakMap
  );
}

// 创建浅层只读对象的函数
/**
 * {@link readonly()} 的浅层版本。
 *
 * 与 {@link readonly()} 不同,没有深转换:只有根级属性是只读的。
 * 属性值以原样存储和暴露-这也意味着具有 ref 值的属性不会被自动展开。
 *
 * @param target - 源对象。
 * @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowreadonly}
 */
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
  return createReactiveObject(
    target,
    true, // 只读的
    shallowReadonlyHandlers, // 浅层只读处理器
    shallowReadonlyCollectionHandlers, // 浅层只读集合处理器
    shallowReadonlyMap // 存储代理对象的 WeakMap
  );
}

// 创建响应式对象的核心函数
function createReactiveObject(
  target: Target, // 目标对象
  isReadonly: boolean, // 是否只读
  baseHandlers: ProxyHandler<any>, // 处理器
  collectionHandlers: ProxyHandler<any>, // 集合处理器
  proxyMap: WeakMap<Target, any>, // 存储代理对象的 WeakMap
) {
  // 如果目标不是对象,返回原值并发出警告
  if (!isObject(target)) {
    if (__DEV__) {
      warn(
        `value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
          target,
        )}`,
      );
    }
    return target;
  }
  
  // 如果目标已经是代理,直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target; 
  }
  
  // 如果已有对应的 Proxy,直接返回
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }

  // 只允许特定的值类型被观察
  const targetType = getTargetType(target);
  if (targetType === TargetType.INVALID) {
    return target; // 如果是无效类型,直接返回
  }

  // 创建代理
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers, // 根据类型选择处理器
  );
  proxyMap.set(target, proxy); // 将目标和代理进行映射
  return proxy; // 返回代理对象
}

// 检查对象是否是响应式代理
/**
 * 检查一个对象是否是 {@link reactive()} 或 {@link shallowReactive()}(或在某些情况下是 {@link ref()})创建的代理。
 *
 * @param value - 要检查的值。
 * @see {@link https://vuejs.org/api/reactivity-utilities.html#isreactive}
 */
export function isReactive(value: unknown): boolean {
  if (isReadonly(value)) {
    return isReactive((value as Target)[ReactiveFlags.RAW]); // 如果是只读对象,则检查其原始值
  }
  
  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE]); // 检查是否是响应式
}

// 检查对象是否为只读
/**
 * 检查传递的值是否是只读对象。只读对象的属性可以改变,但不能直接通过传递的对象进行赋值。
 *
 * @param value - 要检查的值。
 * @see {@link https://vuejs.org/api/reactivity-utilities.html#isreadonly}
 */
export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]); // 检查是否是只读对象
}

// 检查对象是否为浅层响应式
export function isShallow(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW]); // 检查是否为浅层响应式
}

// 检查对象是否为代理
/**
 * 检查一个对象是否是 {@link reactive}、{@link readonly}、{@link shallowReactive} 或 {@link shallowReadonly()} 创建的代理。
 *
 * @param value - 要检查的值。
 * @see {@link https://vuejs.org/api/reactivity-utilities.html#isproxy}
 */
export function isProxy(value: any): boolean {
  return value ? !!value[ReactiveFlags.RAW] : false; // 检查是否为代理
}

// 获取原始对象
/**
 * 返回 Vue 创建的代理的原始对象。
 *
 * `toRaw()` 可以返回 {@link reactive()}、{@link readonly()}、{@link shallowReactive()} 或 {@link shallowReadonly()} 创建的代理的原始对象。
 *
 * 这是一个逃生舱,可以在不产生代理访问/跟踪开销的情况下临时读取或在不触发变更的情况下写入。**不建议持久化引用原始对象。谨慎使用。**
 *
 * @param observed - 请求“原始”值的对象。
 * @see {@link https://vuejs.org/api/reactivity-advanced.html#toraw}
 */
export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]; // 获取原始对象
  return raw ? toRaw(raw) : observed; // 如果找到原始对象则递归获取,否则返回本身
}

// 定义 Raw 类型
export type Raw<T> = T & { [RawSymbol]?: true };

// 标记对象为“原始”,不会被转换为代理
/**
 * 标记一个对象,以使其永远不会被转换为代理。返回对象自身。
 *
 * **警告:** `markRaw()` 与 {@link shallowReactive()} 等浅层 API 一起使用,允许您选择性地选择不进行默认的深度响应/只读转换,并在状态图中嵌入原始、非代理对象。
 *
 * @param value - 要标记为“原始”的对象。
 * @see {@link https://vuejs.org/api/reactivity-advanced.html#markraw}
 */
export function markRaw<T extends object>(value: T): Raw<T> {
  if (!hasOwn(value, ReactiveFlags.SKIP) && Object.isExtensible(value)) {
    def(value, ReactiveFlags.SKIP, true); // 定义跳过标记
  }
  return value; // 返回原始对象
}

// 将值转换为响应式
/**
 * 返回给定值的响应式代理(如果可能)。
 *
 * 如果给定值不是对象,则返回原始值本身。
 *
 * @param value - 需要创建响应式代理的值。
 */
export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value; // 检查是否为对象并返回响应式代理

// 将值转换为只读
/**
 * 返回给定值的只读代理(如果可能)。
 *
 * 如果给定值不是对象,则返回原始值本身。
 *
 * @param value - 需要创建只读代理的值。
 */
export const toReadonly = <T extends unknown>(value: T): DeepReadonly<T> =>
  isObject(value) ? readonly(value) : (value as DeepReadonly<T>); // 检查是否为对象并返回只读代理
  • markRaw 函数的目的在于将一个对象标记为“原始”,从而使其在 Vue 的响应式系统中不会被转换为代理对象。这意味着此对象将不具备响应式或只读的功能。

  • 使用示例

    • 使用 markRaw 标记的对象,当调用 reactive 后不会被转换为响应式对象,isReactive 检查将返回 false。这对于嵌套在响应式对象中的原始对象同样适用。
  • 警告:结合 markRaw 和浅层 API(如 shallowReactive)使用时,开发者可选择性地跳过默认的深度响应/只读转换,以便在响应式状态图中嵌入原始的、非代理的对象。

  • toReactive 函数用于将给定的值转换为响应式代理。如果传入值是一个对象,它会被转换为响应式;如果不是对象,则直接返回原值。

  • 使用场景:适用于在需要时将任意对象转换为响应式对象,便于在 Vue 组件中进行使用,以确保其属性变化能够反应到视图层面。

  • toReadonly 函数返回给定值的只读代理,如果传入值不是对象,则直接返回原值。

  • 使用场景:用于将对象转换为只读状态,以防止后续对该对象及其嵌套属性的修改。这对于确保某些数据在应用中的不可变性非常重要。