概览
vue3中reactive
用于将普通对象转换为响应式对象,它的实现原理是通过Proxy
和Reflect
来实现的。具体的实现文件参见packages\reactivity\src\reactive.ts
。本文会介绍reactive
的相关api如下:
reactive
:将普通对象转换为响应式对象readonly
:将普通对象转换为只读响应式对象isReactive
:判断一个对象是否是响应式对象isReadonly
:判断一个对象是否是只读响应式对象isShallow
:判断一个对象是否是浅层响应式对象isProxy
:判断一个对象是否是代理对象shallowReactive
:创建一个浅层响应式对象shallowReadonly
:创建一个浅层只读响应式对象markRaw
:标记一个对象为原始对象,避免被转换为响应式对象toReadonly
:将一个响应式对象转换为只读响应式对象
源码分析
在分析reactive.ts
的源码之前,先了解如下几个变量,它们分别是:
const reactiveMap = /* @__PURE__ */ new WeakMap(); // 响应式对象的缓存Map
const shallowReactiveMap = /* @__PURE__ */ new WeakMap(); // 浅层响应式对象的缓存Map
const readonlyMap = /* @__PURE__ */ new WeakMap(); // 只读响应式对象的缓存Map
const shallowReadonlyMap = /* @__PURE__ */ new WeakMap(); // 浅层只读响应式对象的缓存Map
isReadonly
isReadonly
用于判断一个对象是否是只读响应式对象,它的实现如下:
function isReadonly(value) {return !!(value && value["__v_isReadonly"]);
}
isReadonly
就是判断参数value
的__v_isReadonly
属性值的布尔值,若为true
则表示是只读对象;否则不是只读对象。
reactive
reactive
的实现如下:
function reactive(target) {// 判断target是否是只读对象,若是,则直接返回if (isReadonly(target)) {return target;}// 调用 createReactiveObject 函数创建响应式对象,并返回return createReactiveObject(target, // 目标对象false, // 是否只读,默认为falsemutableHandlers, // 普通对象的代理处理函数mutableCollectionHandlers, // 集合对象的代理处理函数reactiveMap // 响应式对象的缓存Map);
}
createReactiveObject
reactive
的核心实现是createReactiveObject
函数,它的实现如下:
function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {// 判断target是否是对象,若不是,则弹出警告,并直接返回if (!isObject(target)) {{warn(`value cannot be made ${isReadonly2 ? "readonly" : "reactive"}: ${String(target)}`);}return target;}if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {return target;}const targetType = getTargetType(target);if (targetType === 0 /* INVALID */) {return target;}const existingProxy = proxyMap.get(target);if (existingProxy) {return existingProxy;}const proxy = new Proxy(target,targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);proxyMap.set(target, proxy);return proxy;
}function targetTypeMap(rawType) {switch (rawType) {case "Object":case "Array":return 1 /* COMMON */;case "Map":case "Set":case "WeakMap":case "WeakSet":return 2 /* COLLECTION */;default:return 0 /* INVALID */;}
}
function getTargetType(value) {return value["__v_skip"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap(toRawType(value));
}
createReactiveObject
函数接受5个参数,依次为:目标对象target
、是否只读isReadonly2
、基本类型处理函数baseHandlers
、集合对象的代理处理函数collectionHandlers
、响应式对象的缓存proxyMap
。
createReactiveObject
在确保target
为对象后,会检测target
是一个响应式对象,若是则直接返回;然后会从缓存proxyMap
中获取target
的代理对象,若存在则直接返回该代理对象;否则调用getTargetType
函数判断目标对象target
的类型,若类型为INVALID
,则直接返回target
;否则根据类型创建代理对象,并将其缓存到proxyMap
中,最后返回该代理对象。
getTargetType
的实现也在上面,若target
存在__v_skip
属性且为true
,或者target
不是可扩展的对象,则返回INVALID
;否则根据target
的类型调用targetTypeMap
函数返回类型。
由上targetTypeMap
函数可知,targetTypeMap
函数根据target
的类型返回一个数字,分别表示:
0
:INVALID
,表示target
不是一个对象1
:COMMON
,表示target
是一个普通对象或数组2
:COLLECTION
,表示target
是一个集合对象
综上,可以理解createReactiveObject
函数根据target
创建代理对象的逻辑如下:
- 若
target
为普通对象或数组,则创建普通对象的代理对象,使用baseHandlers
处理函数; - 若
target
为Map
/Set
/WeakMap
/WeakSet
,则创建集合对象的代理对象,使用collectionHandlers
处理函数; - 其余情况,则直接返回
target
。
关于处理器baseHandlers
和collectionHandlers
的实现,会在后面的章节中介绍。
readonly
/shallowReactive
/shallowReadonly
readonly
/shallowReactive
/shallowReadonly
的实现如下:
function readonly(target) {return createReactiveObject(target,true, // 表示只读readonlyHandlers, // 普通对象的只读代理处理函数readonlyCollectionHandlers, // 集合对象的只读代理处理函数readonlyMap // 只读响应式对象的缓存readonlyMap);
}function shallowReactive(target) {return createReactiveObject(target,false, // 不是只读shallowReactiveHandlers, // 普通对象的浅层响应式代理处理函数shallowCollectionHandlers, // 集合对象的浅层响应式代理处理函数shallowReactiveMap // 浅层响应式对象的缓存shallowReactiveMap);
}function shallowReadonly(target) {return createReactiveObject(target,true, // 表示只读shallowReadonlyHandlers, // 普通对象的浅层只读代理处理函数shallowReadonlyCollectionHandlers, // 集合对象的浅层只读代理处理函数shallowReadonlyMap // 浅层只读响应式对象的缓存shallowReadonlyMap);
}
readonly
的实现和reactive
的实现类似,不同就是调用createReactiveObject
函数时,传参不同。
类似的还有shallowReactive
、shallowReadonly
。
它们的不同如下所示
函数 | 只读 | 浅层 | 缓存 | 普通对象的代理方法 | 集合对象的代理方法 |
---|---|---|---|---|---|
reactive | 否 | 否 | reactiveMap | baseHandlers | collectionHandlers |
shallowReactive | 否 | 是 | shallowReactiveMap | shallowReactiveHandlers | shallowCollectionHandlers |
readonly | 是 | 否 | readonlyMap | readonlyHandlers | readonlyCollectionHandlers |
shallowReadonly | 是 | 是 | shallowReadonlyMap | shallowReadonlyHandlers | shallowReadonlyCollectionHandlers |
isReactive
isReactive
的实现如下:
function isReactive(value) {if (isReadonly(value)) {return isReactive(value["__v_raw"]);}return !!(value && value["__v_isReactive"]);
}
isReactive
会先判断value
是否为只读响应式数据,若为只读响应式数据,则会递归调用isReactive
函数判断value
的原始数据是否为响应式数据;否则,会判断value
是否为响应式数据,若为响应式数据,则返回true
,否则返回false
。
isShallow
/ isProxy
/ isReadonly
function isShallow(value) {return !!(value && value["__v_isShallow"]);
}
function isProxy(value) {return value ? !!value["__v_raw"] : false;
}
function isReadonly(value) {return !!(value && value["__v_isReadonly"]);
}
isShallow
/isProxy
/isReadonly
的实现都比较简单,都是判断参数是否存在某个属性,若存在则返回true
,否则返回false
。它们读取的属性__v_isShallow
/__v_raw
/__v_isReadonly
实际上都是针对代理对象的,而它们的逻辑处理也是在处理器方法中实现的,后续会讲到
markRaw
markRaw
用于标记对象为原始对象,这种对象不会被转换为响应式对象,也不会被代理,其实现如下:
function markRaw(value) {if (!hasOwn(value, "__v_skip") && Object.isExtensible(value)) {def(value, "__v_skip", true);}return value;
}
markRaw
首先会判断对象是否可扩展,若可扩展,则会在对象上定义一个__v_skip
属性,打一个标记,值为true
;最后返回该对象。使用reactive(target)
创建响应式对象时,若target
不可扩展,则在调用createReactiveObject
时,其内部调用getTargetType
的返回值就是0
.
def
内部就是调用Object.defineProperty
定义对象上属性
toReadonly
toReadonly
用于将响应式对象转换为只读对象,其实现如下:
const toReadonly = (value) => isObject(value) ? readonly(value) : value;
toReadonly
会判断参数是否为对象,若为是,则会调用readonly
函数将参数转换为只读响应式对象,并返回该代理对象;否则,直接返回参数。这和toReactive
很类似,只是toReactive
会将参数转换为响应式对象,而toReadonly
会将参数转换为只读响应式对象。