reactive
# 概览
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
2
3
4
# isReadonly
isReadonly用于判断一个对象是否是只读响应式对象,它的实现如下:
function isReadonly(value) {
return !!(value && value["__v_isReadonly"]);
}
2
3
isReadonly就是判断参数value的__v_isReadonly属性值的布尔值,若为true则表示是只读对象;否则不是只读对象。
# reactive
reactive的实现如下:
function reactive(target) {
// 判断target是否是只读对象,若是,则直接返回
if (isReadonly(target)) {
return target;
}
// 调用 createReactiveObject 函数创建响应式对象,并返回
return createReactiveObject(
target, // 目标对象
false, // 是否只读,默认为false
mutableHandlers, // 普通对象的代理处理函数
mutableCollectionHandlers, // 集合对象的代理处理函数
reactiveMap // 响应式对象的缓存Map
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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"]);
}
2
3
4
5
6
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"]);
}
2
3
4
5
6
7
8
9
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;
}
2
3
4
5
6
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会将参数转换为只读响应式对象。