跳到主要内容

认识

2023年06月10日
柏拉文
越努力,越幸运

一、认识


Vue.js 3.0 中, 通过 effect 注册副作用函数。基本机制如下所示:

function effect(fn,options = {}){
const effectFn = ()=>{
// effectFn 执行, activeEffect 就会重新设置为此时执行的 effectFn, 保证 effectFn 与 activeEffect 同步
activeEffect = effectFn;
const res = fn();
return res;
}

effectFn.options = options;

if(!options.lazy){
effectFn();
}

return effectFn();
}

二、变量细节


2.1 activeEffect

activeEffect 记录当前激活的副作用函数, 基本逻辑如下:

let activeEffect;

function effect(fn,options = {}){
const effectFn = ()=>{
activeEffect = effectFn;
}
};

2.2 effectStack

2.3 targetMap

targetMap 存储响应式数据对应的副作用函数。结构如下:

targetMap  <WeakMap>{
target1: <Map>{
key1 <Set> [effectFn1,effectFn2,……],
key2 <Set> [effectFn1,effectFn2,……]
},
target2: {
key1 <Set> [effectFn1,effectFn2,……],
key2 <Set> [effectFn1,effectFn2,……]
}
}

2.4 shouldTrack

2.5 ITERATE_KEY

2.6 MAP_KEY_ITERATE_KEY

三、函数细节


3.1 effectFn

effectFneffect 真正的副作用函数, 基本机制如下:

function effect(fn,options){
const effectFn = ()=>{
activeEffect = effectFn; // 执行 effectFn , 立马将 activeEffect 赋值为当前的 effectFn

const res = fn();

return res;
}

effectFn.deps = []; // 用于存储所有包含当前副作用函数的依赖集合
effectFn.options = options;

return effectFn;
}

3.2 cleanup

在每次副作用函数执行时, 根据 effectFn.deps 获取所有相关联的依赖集合, 进而将副作用函数从依赖集合中移除。这样可以避免产生遗留的副作用函数。

function cleanup(effectFn){
for(let i=0; i<effectFn.deps.length; i++){
const deps = effectFn.deps[i];
deps.delete(effectFn);
}
effectFn.deps.length = 0;
}

每次副作用函数执行时, 需要清除副作用哈数相关联的依赖

function effect(fn,options = {}){
const effectFn = ()=>{
activeEffect = effectFn;
cleanup(effectFn);
const res = fn();
return res;
}
}

3.3 track

track 用于注册与 key 相关的副作用函数

3.4 trigger

trigger 用于取出与 key 相关的副作用函数并执行。基本逻辑如下:

function trigger(target,key){
const depsMap = targetMap.get(target);
if(depsMap){
return
}

const effects = depsMap.get(key);
const effectsToRun = new Set(); // 通过 Set 集合存储 effects 并去重, 遍历 Set 而不是直接遍历 effects ,避免 `forEach` 时的无限递归。

// trigger 触发执行的副作用函数与当前正在执行的副作用函数相同, 则不触发执行,避免无限递归调用和栈溢出。
effects && effects.forEach((effectFn)=>{
if(effectFn !== activeEffect){
effectsToRun.add(effectFn);
}
});

effectsToRun.forEach(effectFn => effectFn());
}