vue3的设计真是太妙了,尤雨溪我男神!
逻辑分析
vue3文档上说:
-
reactive:返回对象的响应式副本
-
watchEffect:为了根据响应式状态自动应用和重新应用副作用,我们可以使用
watchEffect
方法。它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
我的理解:
-
reactive会将传入的对象包装成Proxy对象
-
watchEffect中使用到的被reactive包裹的属性更新时,会被trigger触发调用watchEffect
以此通过这种方式实现了数据间的响应式更新。
这张图画的不错,忘了是从哪下载下来的了,如果作者看到请务必联系我(bushi)
代码实现
我的目录结构:
│ index.html
│ index.ts
└─reactive
effect.ts
mutableHandlers.ts
reactive.ts
index.html
<div id="root"></div>
index.ts
import { effect } from "./reactive/effect";
import { reactive } from "./reactive/reactive";
const label = document.querySelector("#root");
const state = reactive({
name: "孙泽辉",
age: 20,
});
effect(() => {
label.innerHTML = `姓名:${state.name},年龄:${state.age}`;
});
setTimeout(() => {
state.name = "孙爷爷";
}, 2000);
setTimeout(() => {
state.age = 21;
}, 4000);
首先要有reactive进行包裹的state,再写一个Effect写好逻辑,Effect第一次会执行一次,之后只有当Effect中用到的state更新时才会执行。
reactive/reactive.ts
import { baseHandler } from "./mutableHandlers";
export function reactive(target: any) {
return createReactiveObject(target);
}
const reactiveMap = new WeakMap<Object, Object>();
function createReactiveObject(target: any) {
const existProxy = reactiveMap.get(target);
return existProxy ? existProxy : new Proxy(target, baseHandler);
}
reactive/effect.ts
export function effect(f: Function) {
const effect = createReactiveEffect(f);
effect();
}
let activeEffect: Function = () => {};
const activeEffectStacked: Function[] = [];
function createReactiveEffect(f: Function) {
const effect = () => {
if (!activeEffectStacked.includes(effect)) {
activeEffectStacked.push(effect);
activeEffect = effect;
try {
f();
} finally {
activeEffectStacked.pop();
activeEffect = activeEffectStacked[activeEffectStacked.length - 1];
}
}
};
return effect;
}
// 保存函数中变量的信息
const targetMap = new WeakMap<Object, Map<string, Set<Function>>>();
export function track(target: any, key: string) {
let depMap = targetMap.get(target);
if (!depMap) {
targetMap.set(target, (depMap = new Map()));
}
let depSet = depMap.get(key);
if (!depSet) {
depMap.set(key, (depSet = new Set()));
}
depSet.add(activeEffect);
console.log(`track:${key}`, targetMap);
}
export function trigger(target: any, key: string) {
let depMap = targetMap.get(target);
if (!depMap) {
return;
}
let depSet = depMap.get(key);
if (!depSet) {
return;
}
console.log(`trigger:${key}`, targetMap);
depSet.forEach((effect) => {
effect && effect();
});
}
第一次执行Effect,经过层层套娃,将Effect临时存到一个EffectStatck中,然后让函数跑起来,如果Effect中有用到state,那一定会触发Proxy的getter函数,在getter里会调用track,track将当前正在跑的Effect存到targetMap这个数据结构中,如上图。
reactive\mutableHandlers.ts
import { track, trigger } from "./effect";
export const baseHandler = {
get(target: any, key: string, receiver: any): any {
console.log("getter:", key);
const value = Reflect.get(target, key, receiver);
track(target, key);
return value;
},
set(target: any, key: string, newValue: any, receiver: any): any {
console.log("setter:", key, newValue);
const result = Reflect.set(target, key, newValue, receiver);
trigger(target, key);
return result;
},
};
当state中的属性有修改时,会触发Proxy的setter函数,在setter里会调用trigger(在reactive/effect.ts中),trigger将变更的属性的Effect从targetMap中全部拿出来,全跑一遍。
没写注释,有空写上。
看看效果吧!
效果
实现效果:state中的数据更新时,会调用Effect更新视图
缺点和不足:
- 没有实现嵌套对象的reactive,应该也就一行代码的事
- 没写注释
- onTrigger、onTrack钩子没写