# Vue3 个人笔记
# 1. 实现 reactive
const proxyMap = new WeakMap();
const enum Reactive_FLAGS {
IS_REACTIVE = "__v_isReactive",
}
function reactive(target: any) {
// 代理的如果不是对象,直接返回
if (!isObject(target)) {
return target;
}
const exitsting = proxyMap.get(target);
// 已经代理过又重新代理源对象,直接返回代理对象
if (exitsting) {
return exitsting;
}
// 已经代理过又重新代理 代理对象,直接返回代理对象
// 这里能命中判断说明是一个 proxy,proxy.xxx 会触发 get 操作,get 操作中会判断如果是此值会返回 true
if (target[Reactive_FLAGS.IS_REACTIVE]) {
return target;
}
// 创建代理
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (key === Reactive_FLAGS.IS_REACTIVE) {
return true;
}
const res = Reflect.get(target, key, receiver);
if (isObject) {
// 递归懒代理,只有在取值时才会深度代理
return reactive(res);
}
return res;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const res = Reflect.set(target, key, value, receiver);
return res;
},
});
proxyMap.set(target, proxy);
return proxy;
}
# 2. 实现 effect
let activeEffect;
class ReactiveEffect {
public fn;
public parent;
public active = true;
public options = {};
public deps = [];
constructor(fn, options) {
this.fn = fn;
this.active = this.active;
this.deps = this.deps;
this.options = options;
}
run() {
if (!this.active) {
return this.fn();
}
try {
// 保证 effect 嵌套的顺序正确
this.parent = activeEffect;
// 将当前的 effect 保存到一个全局变量中,等下调用用户的回调,用户的回调中会触发 get 操作,我们在 get 操作的时候进行依赖收集
activeEffect = this;
// 调用用户的回调
return this.fn();
} finally {
activeEffect = this.parent;
this.parent = undefined;
}
}
}
function effect(fn, options) {
const _effect = new ReactiveEffect(fn, options);
// 先执行一次用户的回调
_effect.run();
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
# 3. 实现依赖收集 track
function reactive(target: any) {
...
const proxy = new Proxy(target, {
get(target, key, receiver) {
...
const res = Reflect.get(target, key, receiver);
// 收集effect
track(target, "get", key);
...
return res;
},
});
...
return proxy;
}
/**
* track后就将用户取的key和activeEffect做了关联,后续用户set值时,我们只需要让对应的effect重新执行即可
* WeakMap{
* 用户对象Object:Map{
* 用户取的key值:Set([activeEffect])
* }
* }
*/
function track(target, opertion, key) {
// 因为我们在执行 this.fn 前将 ReactiveEffect 保存到了 activeEffect 这个全局变量中
if (activeEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
if (!deps.has(activeEffect)) {
deps.add(activeEffect);
// 让activeEffect也记住对应的deps,后续会做清理工作
activeEffect.deps.push(deps);
}
}
}
# 4. 实现依赖更新 trigger
function reactive(target: any) {
...
const proxy = new Proxy(target, {
set(target, key, value, receiver) {
...
const res = Reflect.set(target, key, value, receiver);
// 如果新值和老值不一样,才会去通知更新
if (!Object.is(oldValue, value)) {
// 通知effect重新执行
trigger(target, "set", key, value, oldValue);
}
return res;
},
});
...
return proxy;
}
function trigger(target, opertion, key, value, oldValue) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
// 拿到key对应的set集合,set集合中装了一个个的effect
let effects = depsMap.get(key);
if (effects) {
effects = new Set(effects) // 浅拷贝一份,防止后续清理时一边删除一边添加引起的死循环问题
effects.forEach((effect) => {
// 防止用户在 effect 内部进行更新导致的爆栈问题
if (effect !== activeEffect) {
effect.run();
}
});
}
}
# 5. 实现自定义更新 scheduler
function trigger(target, opertion, key, value, oldValue) {
...
if (effects) {
effects.forEach((effect) => {
if (effect !== activeEffect) {
// 如果用户传递了scheduler,优先执行用户的,不执行内部的run
if (effect.options?.scheduler) {
effect.options.scheduler();
} else {
effect.run();
}
}
});
}
}
# 6. 条件切换后清除多余的 effect
# 6.1 例子
// flag a b 均为响应式数据
effect(() => {
flag ? a : b;
});
setTimeout(() => {
// 条件发生变化后flag变成false,那么此时重新run effect 的时候应该清除a对应的effect依赖
flag = false;
setTimeout(() => {
// a发生变化不应该导致更新
a += 1;
});
});
# 6.2 实现
function cleanupEffect(effect) {
const { deps } = effect;
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect);
}
deps.length = 0;
}
}
class ReactiveEffect {
...
run() {
try {
...
activeEffect = this;
// 每次run前都清除之前对应的依赖,run执行的时候重新收集
cleanupEffect(this);
return this.fn();
} finally {
...
}
}
}
# 7. 流程大逻辑梳理
- 代理创建
Proxy
-> 用户取值时触发get
操作 - 调用
track
,将用户取的值和effect
进行关联 - 用户设置值时,调用
trigger
,重新执行对应的effect