认识
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
effectFn
是 effect
真正的副作用函数, 基本机制如下:
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());
}