带着问题阅读源码Vue3.2-reactive实现原理
vue3提供了一个新的API-reactive,用于定义响应式对象。
const book = reactive({ title: 'Vue 3.2 reative 实现原理' })
用法很简单,reactive包裹一个对象,返回的数据就是响应式对了。那么我们的问题来
-
vue3是如何转变成响应对象的呢?
-
vue3内部数据是如何收集依赖以及更新依赖的?
createReactiveObject的实现
// 存储 reactive对象
const reactiveMap = new WeakMap();
// reactive函数入口
function reactive(target) {
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
// proxyMap 就是 reactiveMap
// target 已经存在 reactiveMap中,此时直接返回存在的数据
// 说明 同一个对象即时多次 reactive 调用,其返回的结果都是同一个响应式对象。
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 通过 Proxy 代理对象
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
// 存入 reactiveMap 中
proxyMap.set(target, proxy);
return proxy;
}
如果不了解Proxy这个api,可以点击Proxy
vue3通过 Proxy 这个api,把普通对象转换成响应式对象。在vue2中是的defineProperty这个api把普通对象转换成响应式对象,这个api存在缺陷,例如不能代理数组,当响应式对象添加属性的时候,就不是响应式对象了。
get的实现: createGetter
// get 的实现
const get = createGetter();
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// 处理数组
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
// 处理 对象
const res = Reflect.get(target, key, receiver);
if (!isReadonly) {
// 开始收集 依赖
track(target, "get" /* GET */, key);
}
if (isObject(res)) {
// 处理嵌套的对象:例如 {id: 1, value: {name: '嵌套', age: 18}}
// 注意此处 相对于vue2,此处延迟代理
// 首页 proxy 这个api只会代理第一层的数据。然后我们只有在读取数据,触发get,才会把嵌套的对象转换成响应式数据
// vue2的处理 创建get的时候,通过递归把所有数据都全部转换响应式对象
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
get中处理数组情况:createArrayInstrumentations
function createArrayInstrumentations() {
const instrumentations = {};
// 处理 查询数据的 api
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
instrumentations[key] = function (...args) {
//toRaw 从Reactive或Ref中得到原始数据
// toRaw作用 做一些不想被监听的事情(提升性能)
const arr = toRaw(this);
for (let i = 0, l = this.length; i < l; i++) {
track(arr, "get" /* GET */, i + '');
}
// we run the method using the original args first (which may be reactive)
const res = arr[key](...args);
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
return arr[key](...args.map(toRaw));
}
else {
return res;
}
};
});
// 处理 数组更改的 api
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
instrumentations[key] = function (...args) {
// 开启依赖收集
pauseTracking();
// 变更的数据
//toRaw 从Reactive或Ref中得到原始数据
// toRaw作用 做一些不想被监听的事情(提升性能)
const res = toRaw(this)[key].apply(this, args);
// 关闭依赖收集
resetTracking();
return res;
};
});
return instrumentations;
}
数组树上比较特殊,'push', 'pop', 'shift', 'unshift', 'splice' 这个五个api会更改数组本身,
includes', 'indexOf', 'lastIndexOf'这个三个api是查询的数据,存在查询的时候数组已经变更的情况
依赖收集:track
// 此处又有了 一个 WeakMap
const targetMap = new WeakMap();
// set 存 effct
const createDep = (effects) => {
const dep = new Set(effects);
dep.w = 0;
dep.n = 0;
return dep;
};
function track(target, type, key) {
// 与上面 createReactiveObject 处理有点类似
// 防止重复收集
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 查找 target 中的 key,第一次的话 会默认一个空的set
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = createDep()));
}
// 收集 副作用 effect
trackEffects(dep);
}
function trackEffects(dep, debuggerEventExtraInfo) {
let shouldTrack = false;
if (shouldTrack) {
// 把当前 activeEffect 存入 set 中的
// activeEffect 是 ReactiveEffect 的实例对象,它长这样子:
// {
// fn: Function,
// scheduler: scheduler,
// active: true,
// deps: [],
// run() {},
// stop() {},
// }
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
存储数据有点绕,先是 用WeakMap存target,其中key是target,value是 Map
Map 的key是 target 中的key,value是 Set
Set里面存的是 当前 activeEffect
set的实现: createSetter
// set 的实现
const set = createSetter();
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
// 获取旧值
//toRaw 从Reactive或Ref中得到原始数据
// toRaw作用 做一些不想被监听的事情(提升性能)
let oldValue = target[key];
if (!shallow) {
value = toRaw(value);
oldValue = toRaw(oldValue);
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
// 获取 key,主要处理数组 NaN - 特殊情况
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
// 设置新值
const result = Reflect.set(target, key, value, receiver);
// 确保依赖的数据源是同一个 对象
if (target === toRaw(receiver)) {
if (!hadKey) {
// 新增数据
// 更新依赖
trigger(target, "add" /* ADD */, key, value);
}
else if (hasChanged(value, oldValue)) {
// 变更数据
// 更新依赖
trigger(target, "set" /* SET */, key, value, oldValue);
}
}
return result;
};
}
set 的时候比较简单,获取key,设置新值。如果是新增数据,触发新增依赖,如果更改数据,触发变更依赖。
更新依赖:trigger
function trigger(target, type, key, newValue, oldValue, oldTarget) {
// 从 存储 target中的WeakMap 获取目标对象
const depsMap = targetMap.get(target);
if (!depsMap) {
// never been tracked
return;
}
// 数组 暂存依赖数据
let deps = [];
if (type === "clear" /* CLEAR */) {
// 清空依赖
deps = [...depsMap.values()];
}
else if (key === 'length' && isArray(target)) {
// 处理数组 通过 length 变更的情况
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newValue) {
deps.push(dep);
}
});
}
else {
// 通过 schedule runs 的情况
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
deps.push(depsMap.get(key));
}
// also run for iteration key on ADD | DELETE | Map.SET
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));
}
}
else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'));
}
break;
case "delete" /* DELETE */:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
case "set" /* SET */:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY));
}
break;
}
}
const eventInfo = { target, type, key, newValue, oldValue, oldTarget }
;
if (deps.length === 1) {
if (deps[0]) {
{
// 执行 更新依赖 effect
triggerEffects(deps[0], eventInfo);
}
}
}
else {
// 收集的依赖不是只有一个的情况
// 一种情况是数据清空
// 另外一种是存在多个依赖
const effects = [];
for (const dep of deps) {
if (dep) {
effects.push(...dep);
}
}
{
triggerEffects(createDep(effects), eventInfo);
}
}
}
function triggerEffects(dep, debuggerEventExtraInfo) {
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
// 组件更新, doWatch, computed设置这个属性
if (effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo));
}
// scheduler 调动更新
// doWatch 里面设置
if (effect.scheduler) {
effect.scheduler();
}
else {
// 最后是 effect run
effect.run();
}
}
}
}
更新依赖有点复杂
首先是从 targetMap 找到target对应的数据,没有找到就不会更新依赖,直接返回。
如果找到了则声明一个数组 deps 存储依赖数据,接下就是处理依赖数据的流程了
如果是 clear, 则 deps 置空
如果是 通过length更改数据 的情况,则遍历数组,把小于 newValue 的索引push到deps 数组中
如果 key 不等于 void 0,(这是常用的地方:schedule runs),则是把depsMap.get(key) push到deps数组中
剩余是处理迭代器中add、set、delete 的逻辑了。
处理好依赖后,则会调用 triggerEffects,它会把每一个依赖中的 effect 哪出来执行一遍
effect.onTrigger 这个属性等后续 深入组件更新、doWatch原理、compute原理的时候会需要用上这个属性
如果effect上有 onTrigger这属性,则先执行effect.onTrigger
上面完成后,如果effect上有scheduler属性,则会执行effect.schedule,否则会执行effect.run
scheduler 是为了提供我想要让你什么时候执行就什么时候执行的能力,也就是可以自己调度的能力。
总结
vue3通过Proxy和reflect这两个api把普通对象转换成响应式对象。
当数据读取的时候,会触发get收集依赖,收集存储在一个WeakMap中,其中key是target(目标对象),value是一个Map数据结构,这个Map的key是我们读取数据对应的key(target的key),value是一个Set数据结构。Set存储的是activeEffect。
当数据变更的时候,会触发set更新依赖,更新依赖的时候,会先去WeakMap中找到target对应的数据,找到后经过一番依赖数据标准后,遍历依赖,执行依赖的每一个activeEffect
自问自答系列:
- 为什么用weakMap 存储响应式对象?
用weakMap的用处防止内存泄漏,当变量的引用不存在的时候,自动会清楚内存;缓存响应式数据是为了防止重复收集
- 依赖数据存储分别用了 weakMap、Map、Set三种数据结构存储,为什么要这样设计呢?
用weakMap与上面原因一样,一是防止内存泄漏,二是防止重复收集
用Map存储而不是用Object,是因为map的键可以是任意值,而Object 的键必须是一个 String 或是Symbol
用Set当然是为了去重了,Set存储的是数据不能重复的。