本文基于Vue 3.2.30
版本源码进行分析
为了增加可读性,会对源码进行删减、调整顺序、改变部分分支条件的操作,文中所有源码均可视作为伪代码
由于ts版本代码携带参数过多,不利于展示,大部分伪代码会取编译后的js代码,而不是原来的ts代码
本文内容
Set数据
响应式测试代码调试与Vue3对应源码总结Map数据
响应式测试代码调试与Vue3对应源码总结
本篇文章不对ref类型
、shallow类型
、readonly
类型进行总结分析
本篇文章主要集中总结Vue3对于Map和Set数据一些常见方法代理时的源码处理,不深入探究原理
本文尽可能对源码中涉及到的读写操作的源码进行列举和总结,难免会有遗漏
前置说明
在上一篇Vue3源码-响应式系统-依赖收集和派发更新流程浅析文章中,我们梳理了整个响应式系统依赖收集和派发更新的流程,还简单地介绍了Proxy
以及Reflect
,明白了响应式的基本原理就是拦截target
一些方法,比如get
,比如set
,然后进行依赖收集和派发更新
但是我们并没有对Vue3中响应式数据类型的分类,响应式数据常见属性的拦截等源码进行分析,本篇文章将基于Vue3源码各种非原始值的数据响应式进行总结,主要研究的源代码核心部分如下所示:
function reactive(target) {
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
return proxy;
}
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
};
const mutableCollectionHandlers = {
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
};
从new Proxy(target)
中可以发现,响应式监听数据分为两种targetType=2
以及targetType=1
,从上面和下面代码块可以得知,当target=2
时,即数据类型为Map/Set/WeapMap/WeapSet
时,new Proxy(target, collectionHandlers)
,本文将基于collectionHandlers
进行分析
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 */;
}
}
核心源码
由于集合类型的方法较为复杂,因此跟Object/Array不同的是,我们直接从源码分析它们响应式的逻辑,而不从读/写方面分析
主要分为两个部分,第一个部分是get
、size
、has
等等常规方法
const mutableInstrumentations = {
get(key) {
return get$1(this, key);
},
get size() {
return size(this);
},
has: has$1,
add,
set: set$1,
delete: deleteEntry,
clear,
forEach: createForEach(false, false)
};
第二部分是keys
、values
、entries
等迭代方法
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator];
iteratorMethods.forEach(method => {
mutableInstrumentations[method] = createIterableMethod(method, false, false);
});
function createIterableMethod(method, isReadonly, isShallow) {
return function (...args) {
const rawTarget = toRaw(target);
//......
track(rawTarget, "iterate" /* ITERATE */, isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY);
return {
next() {
const { value, done } = innerIterator.next();
return done ? { value, done }
: { value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), done };
},
[Symbol.iterator]() {
return this;
}
};
};
}
迭代器和可迭代对象
为下面的forEach、entries、keys、values的分析做理论准备
迭代器/迭代器协议 iterator protocol
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
只有实现了一个拥有以下语义的 next()
方法,一个对象才能成为迭代器
属性 | 值 |
---|---|
next | 一个无参数的或者可以接受一个参数的函数,返回一个应当拥有以下两个属性的对象:done(boolean) next() 方法必须返回一个对象,该对象应当有两个属性: done 和 value ,如果返回了一个非对象值(比如 false 或 undefined ),则会抛出一个 TypeError 异常("iterator.next() returned a non-object value ")。 |
可迭代对象 iterable protocol
要成为可迭代对象,一个对象必须实现@@iterator
方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为@@iterator
的属性,可通过常量 Symbol.iterator
访问该属性:
属性 | 值 |
---|---|
[Symbol.iterator] | 一个无参数的函数,其返回值为一个符合迭代器协议 的对象。 |
示例说明
typeof aGeneratorObject.next;
// 返回"function", 因为有一个 next 方法,所以这是一个迭代器
typeof aGeneratorObject[Symbol.iterator];
// 返回"function", 因为有一个 @@iterator 方法,所以这是一个可迭代对象
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// 返回 true,因为 @@iterator 方法返回自身(即迭代器),所以这是一个格式良好的可迭代对象
接收可迭代对象的内置API
很多 API 接受可迭代对象作为参数,例如:
- new Map([iterable])
- new WeakMap([iterable])
- new Set([iterable])
- new WeakSet([iterable])
- Promise.all(iterable)
- Promise.race(iterable)
- Array.from(iterable)
new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2); // "b"
let myObj = {};
new WeakMap([
[{}, 'a'],
[myObj, 'b'],
[{}, 'c']
]).get(myObj); // "b"
new Set([1, 2, 3]).has(3); // true
new Set('123').has('2'); // true
new WeakSet(function* () {
yield {}
yield myObj
yield {}
}()).has(myObj); // true
可迭代对象的应用
一些语句和表达式需要可迭代对象,比如 for...of 循环、展开语法、yield*,和解构赋值
for(let value of ["a", "b", "c"]){
console.log(value); // "a", "b", "c"
}
[..."abc"]; // ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];
}
gen().next(); // { value: "a", done: false }
[a, b, c] = new Set(["a", "b", "c"]);
for....in和for....of的区别
**for...in**
语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性(原型链上的key
)**for...of**
语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句,遍历可迭代对象定义要迭代的数据
forEach循环无法中途跳出,break和return都无法中断循环,而for...in和for...of可以
for...in是无序的,for...of是有序的
自定义实现一个可迭代对象和迭代器协议
核心结构
var myIterator = {
next: function() {
// ...
},
[Symbol.iterator]: function() {
return this;
}
};
简单示例
function MyArray(array) {
let nextIndex = 0;
return {
next: function () {
// 应该done:true将nextIndex置为0
return nextIndex < array.length ? {
value: array[nextIndex++],
done: false
} : {
done: true
};
},
[Symbol.iterator]: function () {
return this;
}
};
}
var initArray = new MyArray(["我是第一个", "2", "我是第三个"]);
for (let value of initArray) {
console.log(value);
}
forEach:proxy.forEach(function(value, key, target), thisArg)
测试代码
effect(() => {
proxyMap.forEach((...args) => {
console.warn("proxyMap.forEach的item结果是");
console.info(...args);
});
});
源码分析
- 触发
Proxy.get()
响应,触发track(rawTarget, "iterate", ITERATE_KEY)
的依赖收集 callback
返回数据可能也是Object
类型,包括key
和value
,都得使用toReactive(value)
+toReactive(key)
进行转化为响应式对象后返回forEach
可以传递第二个参数thisArg
,callback.call
调用时需要显式callback.call(thisArg)
forEach
的第一个参数function(value, key, target)
中第三个参数是触发forEach
调用的对象,传递代理对象observed
function createForEach(isReadonly, isShallow) {
return function forEach(callback, thisArg) {
const observed = this;
const target = observed["__v_raw" /* RAW */];
const rawTarget = toRaw(target);
track(rawTarget, "iterate" /* ITERATE */, ITERATE_KEY);
return target.forEach((value, key) => {
// important: make sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
// 2. the value received should be a corresponding reactive/readonly.
return callback.call(thisArg, toReactive(value), toReactive(key), observed);
});
};
}
entries/Symbol.iterator/keys/values
测试代码 & 原始方法逻辑分析
entries
:Map
和Set
都会返回一个可迭代对象,可以使用for(let [key,value] of data.entries())
进行遍历Map.entries()
返回的是[key, value]
数据Set.entries()
返回的是[value, value]
数据
keys
:由于Set
没有key
,因此Set.keys()等同于Set.values()
,Map.keys()
获取所有的keys
values
:由于Set
没有key
,因此Set.keys()等同于Set.values()
,Map.values()
获取所有的value
Symbol.iterator
:Set[Symbol.iterator]
跟Set.entries()
表现不一致,如下面代码块所示,Set[Symbol.iterator]
更接近于Set.values
,Set[Symbol.iterator] === Set.entries
为false
Map[Symbol.iterator]
与Map.entries()
表现一致,Map[Symbol.iterator] === Map.entries
为true
const mySet = new Set(); mySet.add("0");mySet.add("1");
const setIter = mySet[Symbol.iterator]();
console.log(setIter.next().value); // "0"
for(let value of mySet[Symbol.iterator]()) {
// Symbol.iterator let...of 0
// Symbol.iterator let...of 1
console.warn("Symbol.iterator let...of", value);
}
const set1 = new Set(); set1.add("0");set1.add("1");
const iterator1 = set1.entries();
console.log(iterator1.next().value); // ['0', '0']
for(let value of set1.entries()) {
// Symbol.iterator let...of ['0', '0']
// Symbol.iterator let...of ['1', '1']
console.warn("entries let...of", value);
}
源码分析
返回数据
- 根据上面上面迭代器和可迭代对象的理论分析,我们需要实现一个可迭代对象和实现迭代器协议,即
next()
和[Symbol.iterator]()
- 根据
entries
/Symbol.iterator+Map
进行数据的返回:[value[0], value[1]]
,返回数据都进行toReactive()
响应式包裹 - 其余的
method=keys/values/Symbol.iterator+Set
,都只返回一个数据,即value
,返回数据都进行toReactive()
响应式包裹
依赖追踪
- 如果使用
keys
,那么我们就只需要关注key
的变化,不需要关注value
的变化,因此使用MAP_KEY_ITERATE_KEY
进行依赖追踪,触发track(rawTarget, "iterate", MAP_KEY_ITERATE_KEY)
- 如果使用
values/entries/Symbol.iterator
,都需要关注value
的变化,因此使用ITERATE_KEY
进行依赖追踪,触发track(rawTarget, "iterate", ITERATE_KEY)
function createIterableMethod(method, isReadonly, isShallow) {
return function (...args) {
const target = this["__v_raw" /* RAW */];
const rawTarget = toRaw(target);
const targetIsMap = isMap(rawTarget);
const isPair = method === 'entries' || (method === Symbol.iterator && targetIsMap);
const isKeyOnly = method === 'keys' && targetIsMap;
const innerIterator = target[method](...args);
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
!isReadonly &&
track(rawTarget, "iterate" /* ITERATE */, isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY);
return {
// iterator protocol
next() {
const { value, done } = innerIterator.next();
return done
? { value, done }
: {
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done
};
},
// iterable protocol
[Symbol.iterator]() {
return this;
}
};
};
}
总结
根据Map
/Set
数据类型的特性,仿写对应的next()
和[Symbol.iterator]
实现迭代器和可迭代对象,在仿写的基础上,实现对应的依赖收集逻辑和返回数据响应式包裹逻辑
size:proxy.size
测试代码
effect(() => {
console.log("map.size", proxyMap.size);
});
源码分析
触发Proxy.size()
响应,然后进行track(target, "iterate", ITERATE_KEY)
的依赖收集
跟Object/Array
不同的是,Map/Set
不能通过代理对象访问属性,会报Uncaught TypeError: Method get Map.prototype.size called on incompatible receiver undefined
的错误
因此Reflect.get()
的第三个参数receiver
需要显示更改为原始对象target
function size(target, isReadonly = false) {
target = target["__v_raw" /* RAW */];
track(toRaw(target), "iterate" /* ITERATE */, ITERATE_KEY);
return Reflect.get(target, 'size', target);
}
get:proxy.get("key")
测试代码
const object = {
"item1": 1,
"item2": 333
}
const map = new Map(Object.entries(object));
const objectKey = { test: 1 };
const reactiveKey = reactive(objectKey);
map.set(reactiveKey, "testReactive");
const proxy = reactive(map);
const objectKeyProxy = { testProxy: 1 };
const reactiveKeyProxy = reactive(objectKeyProxy);
proxy.set(reactiveKeyProxy, "testProxyReactive");
effect(() => {
console.log("map.get(普通number)", proxy.get(0));
console.log("map.get(proxy对象)", proxy.get(reactiveKey));
console.log("map.get(原始对象)", proxy.get(objectKey));
console.log("map.get(proxy对象之后才set的)", proxy.get(reactiveKeyProxy));
});
代码分析
- 触发
Proxy.get()
响应,如果key是响应式
,需要进行key
的track
,即track(target, "get", key)
+track(target, "get", rawKey)
的依赖收集 - 触发
Proxy.get()
响应,如果key不是响应式
,则进行track(target, "get", rawKey)
的依赖收集 - 除了上面的依赖收集之外,需要使用原始对象进行
get()
方法的调用,即target.get(key)
- 如果返回对象
target.get(key)
是Object类型
,需要转化为响应式的,调用reactive(value)
- 由于
key
可能是响应式的Object Key
,因此需要判断has.call(rawTarget, key)
是否为true
,如果不为true
,需要去进行has.call(rawTarget, rawKey)
的判断,然后进行具体的get
请求返回要求的值
function get(target, key, isReadonly = false, isShallow = false) {
target = target["__v_raw" /* RAW */];
const rawTarget = toRaw(target);
const rawKey = toRaw(key);
if (key !== rawKey) {
!isReadonly && track(rawTarget, "get" /* GET */, key);
}
!isReadonly && track(rawTarget, "get" /* GET */, rawKey);
const { has } = getProto(rawTarget);
// const toReactive = (value) => isObject(value) ? reactive(value) : value;
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
if (has.call(rawTarget, key)) {
return wrap(target.get(key));
}
else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey));
}
else if (target !== rawTarget) {
// #3602 readonly(reactive(Map))
// ensure that the nested reactive `Map` can do tracking for itself
target.get(key);
}
}
has:proxy.has("key")
测试代码
const object = {
"item1": 1,
"item2": 333
}
const map = new Map(Object.entries(object));
const proxy = reactive(map);
effect(() => {
console.log("map.has", proxy.has(4));
});
源码分析
- 触发
Proxy.has()
响应,如果key是响应式
,需要进行key
的track
,即track(target, "get", key)
+track(target, "get", rawKey)
的依赖收集 - 触发
Proxy.has()
响应,如果key不是响应式
,则进行track(target, "get", rawKey)
的依赖收集
function has(key, isReadonly = false) {
const target = this["__v_raw" /* RAW */];
const rawTarget = toRaw(target);
const rawKey = toRaw(key);
if (key !== rawKey) {
!isReadonly && track(rawTarget, "has" /* HAS */, key);
}
!isReadonly && track(rawTarget, "has" /* HAS */, rawKey);
return key === rawKey
? target.has(key)
: target.has(key) || target.has(rawKey);
}
Set.add
测试代码
effect(() => {
console.log("Set.add", proxySet.add(5));
});
源码分析
触发
Proxy.add()
响应,如果value不存在
,使用原生Set.add()
方法,然后trigger(target, "add", value, value)
的依赖收集- 触发
Proxy.size()
所依赖收集的ITERATE_KEY
,触发所有涉及到size
的effects
重新执行(元素新增,size
也新增了) - 由于不是
Map
数据,因此不会触发MAP_KEY_ITERATE_KEY
,MAP_KEY_ITERATE_KEY
关联的是Map
的key
数据,是Map
的forEach
以及for...in
所收集的依赖
- 触发
- 如果
value存在
,则什么都不操作
function add(value) {
value = toRaw(value);
const target = toRaw(this);
const proto = getProto(target);
const hadKey = proto.has.call(target, value);
if (!hadKey) {
target.add(value);
trigger(target, "add" /* ADD */, value, value);
}
return this;
}
function trigger(target, type, key, newValue, oldValue, oldTarget) {
if (key !== void 0) {
deps.push(depsMap.get(key));
}
switch (type) {
case "add" /* ADD */:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
}
}
Map.set
测试代码
effect(()=> {
console.log("Map.set", proxy.set(0, 44444));
});
源码分析
Map.set
会判断是否存在key
,如果存在,则触发trigger(target, "add" /* ADD */, key, value)
Map.set
会判断是否存在key
,如果不存在,则触发trigger(target, "set" /* SET */, key, value, oldValue)
function set(key, value) {
value = toRaw(value);
const target = toRaw(this);
const { has, get } = getProto(target);
let hadKey = has.call(target, key);
if (!hadKey) {
key = toRaw(key);
hadKey = has.call(target, key);
}
else {
checkIdentityKeys(target, has, key);
}
const oldValue = get.call(target, key);
target.set(key, value);
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value);
}
else if (hasChanged(value, oldValue)) {
trigger(target, "set" /* SET */, key, value, oldValue);
}
return this;
}
新增key
以及value
trigger(target, "add" /* ADD */, key, value)
:
depsMap.get(key)
的所有effecs
重新执行,此时key
=Map数据中新的key
- 触发跟
size
相关的ITERATE_KEY
的effecs
重新执行(增加key
会影响Map.size
的属性,而循环遍历,比如forEach
、for..of
都需要获取Map.size
,因为遍历得要知道到底需要遍历item
为几个) - 触发跟
keys
相关的MAP_KEY_ITERATE_KEY
的effecs
重新执行(由上面entries/Symbol.iterator/keys/values
的分析可以知道,这个MAP_KEY_ITERATE_KEY
是为了关联key
集合而产生的,因为对于一些遍历来说,比如for(let key of map.keys())
,我们只需要关注keys
,不需要关注values
的变化)
function trigger(target, type, key, newValue, oldValue, oldTarget) {
if (key !== void 0) {
deps.push(depsMap.get(key));
}
switch (type) {
case "add" /* ADD */:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
}
}
更新已经存在的key
的value
trigger(target, "set" /* SET */, key, value, oldValue)
:
depsMap.get(key)
的所有effecs
重新执行- 跟
size
相关的ITERATE_KEY
的effecs
重新执行
function trigger(target, type, key, newValue, oldValue, oldTarget) {
if (key !== void 0) {
deps.push(depsMap.get(key));
}
switch (type) {
case "set" /* SET */:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY));
}
break;
}
}
按照常识来说,更新已经存在的key
的value
不应该触发跟size
相关的ITERATE_KEY
的effecs
重新执行,但是源码确实这么做了,如下面示例代码,每次更新已经存在的key
的value
后,虽然proxyMap.size
不会变化,但是effect
会重新执行
已经提了github vue3-issue,由于本文基于Vue 3.2.30
版本源码进行分析,就算后续版本修复该问题,Vue 3.2.30
版本仍然也是有这个问题,因此这里总结为:在Vue 3.2.30
版本下,任何Map.set
操作都会触发跟size
相关的ITERATE_KEY
的effecs
重新执行,包括Map.size
/forEach
、for..of
等遍历方法
const map = new Map();
map.set("item1", 1);
map.set("item2", 2);
const proxyMap = reactive(map);
effect(()=> {
console.info("proxyMap.size", proxyMap.size);
});
onMounted(()=> {
setInterval(() => {
proxyMap.set("item1", new Date().getTime());
}, 2000);
});
delete
测试代码
effect(() => {
console.log("size.delete", proxy.delete("item2"));
});
源码分析
- 触发
Proxy.delete(key)
响应,直接调用原始对象target.delete(key)
Map.delete
会判断是否存在key
/toRaw(key)
,如果存在,则触发trigger(target, "delete" /* DELETE */, key, undefined, oldValue)
Map.delete
会判断是否存在key
,如果不存在,则判断toRaw(key)
是否存在,如果不存在,则什么都不操作
function deleteEntry(key) {
const target = toRaw(this);
const { has, get } = getProto(target);
let hadKey = has.call(target, key);
if (!hadKey) {
key = toRaw(key);
hadKey = has.call(target, key);
} else {
// 检测 key === toRaw(key),进行警告
checkIdentityKeys(target, has, key);
}
const oldValue = get ? get.call(target, key) : undefined;
// forward the operation before queueing reactions
const result = target.delete(key);
if (hadKey) {
trigger(target, "delete" /* DELETE */, key, undefined, oldValue);
}
return result;
}
派发更新
trigger(target, "delete" /* DELETE */, key, undefined, oldValue);
:
depsMap.get(key)
的所有effecs
重新执行- 触发跟
size
相关的ITERATE_KEY
的effecs
重新执行(删除key
会影响Map.size
的属性,而循环遍历,比如forEach
、for..of
都需要获取Map.size
,因为遍历得要知道到底需要遍历item
为几个) - 触发跟
keys
相关的MAP_KEY_ITERATE_KEY
的effecs
重新执行(由上面entries/Symbol.iterator/keys/values
的分析可以知道,这个MAP_KEY_ITERATE_KEY
是为了关联key
集合而产生的,因为对于一些遍历来说,比如for(let key of map.keys())
,我们只需要关注keys
,不需要关注values
的变化)
function trigger(target, type, key, newValue, oldValue, oldTarget) {
if (key !== void 0) {
deps.push(depsMap.get(key));
}
switch (type) {
case "delete" /* DELETE */:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
}
}
clear
测试代码
effect(() => {
console.log("Map.clear", proxy.clear());
});
源码分析
- 触发
Proxy.clear()
响应,直接调用原始对象target.clear()
- 然后判断没清空之前的
size是否为0
,如果不为0,则trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget)
function clear() {
const target = toRaw(this);
const hadItems = target.size !== 0;
const oldTarget = isMap(target)
? new Map(target)
: new Set(target)
;
// forward the operation before queueing reactions
const result = target.clear();
if (hadItems) {
trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget);
}
return result;
}
派发更新
trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget)
:
调用所有该对象的depsMap
,触发该对象涉及到的所有effects
重新执行
function trigger(target, type, key, newValue, oldValue, oldTarget) {
const depsMap = targetMap.get(target);
if (!depsMap) {
// never been tracked
return;
}
let deps = [];
if (type === "clear" /* CLEAR */) {
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()];
}
}
总结
跟Object/Array
不同,Map/Set
无法调用Reflect.set(target, key, receiver)
/Reflect.get(target, key, receiver)
进行数据的操作,只能使用原生数据target
进行一系列的操作,代理Map/Set
多个方法时,主要有4个步骤:
- 转化
key
/value
为toRaw()
,对key/toRaw(key)
以及value/toRaw(value)
进行处理 - 调用
Map/Set
原生的方法进行数据的处理 - 根据方法适配不同的依赖收集和派发更新
- 返回值应该判断是否需要
toReactive(object)
包裹
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。