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 主要部分解析:
-
判断只读:
- 如果传入的目标对象已经是只读 (
readonly
),则直接返回该对象,这样可以避免对其进行再次处理。
- 如果传入的目标对象已经是只读 (
-
创建响应式对象:
- 如果目标对象不是只读,将其传递给
createReactiveObject
函数,这个函数将负责创建并返回一个响应式的代理对象。
- 如果目标对象不是只读,将其传递给
-
参数说明:
target
:需要被转为响应式的目标对象。false
:表示这个对象是可变的不是只读。mutableHandlers
和mutableCollectionHandlers
是处理响应式行为的处理器,分别用于常规对象和集合对象。
createReactiveObject
函数
createReactiveObject
函数是整个响应式处理的核心逻辑之一,负责真正执行对象的转化。
createReactiveObject 函数主要部分解析:
-
检查目标对象的有效性:
- 如果目标不是一个对象,返回原值,同时在开发环境下警告开发者。
-
代理重复检测:
- 检查目标对象是否已经有对应的代理,避免多次代理同一对象。
-
对象类型检查:
- 使用
getTargetType
方法获取目标对象的类型,确保只处理支持的对象类型(如普通对象、集合)。
- 使用
-
创建响应式代理:
- 使用 JavaScript 的
Proxy
来创建目标对象的代理,并根据目标类型选择合适的处理器。
- 使用 JavaScript 的
-
存储代理关系:
- 将代理对象存入 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
函数返回给定值的只读代理,如果传入值不是对象,则直接返回原值。 -
使用场景:用于将对象转换为只读状态,以防止后续对该对象及其嵌套属性的修改。这对于确保某些数据在应用中的不可变性非常重要。