vue3 effect 实现思路
首先了解这张图
const state = reactive({ name: 'cld', age: 26, arr: [1, 2, 3], obj: { attr1: 'attr1', attr2: 'attr2' } });
effect(()=>{
console.log('effect内执行:',state.obj.attr1)
})
state.obj.attr1='cld2'
大概思路清楚了 开始分析代码
创建effect函数
传入一个函数与options对象
export function effect (fn, options = {}) {
const effect = createReactiveEffect(fn, options);
if (!options.lazy) {
effect();
}
return effect;// 非立即执行的情况
}
createReactiveEffect内部处理
let uid = 0; // 给每个effect加唯一标识
let activeEffect // 当前正在执行的effect;
const effectStack = []; //判断当我目前effect正在执行的时候 不要继续添加 不要再执行导致死循环
function createReactiveEffect (fn, options) {
const effect = function reactiveEffect (params) {
if (!effectStack.includes(effect)) { // 防止死循环
try { // fn可能会出错 要退出
effectStack.push(effect);
activeEffect = effect;
fn(); // 执行传进来的函数
} finally {
effectStack.pop();
activeEffect = undefined;
}
}
};
effect.options = options;
effect.id = uid++; // 唯一标识
effect.deps = []; // 存储会导致它执行的数据
return effect;
}
- 当我执行effect的时候 会执行上面的函数
- 在effect内部获取数据的时候 会执行new proxy 中的 get函数
- 主要是 new proxy 里面的 get 函数的拦截处理触发track函数
- 去收集该属性相关的effect在数组中
function get (target, key, receiver) { // proxy+reflect
const res = Reflect.get(target, key, receiver);// == target[key];
// 主要是这里的触发函数
track(target, 'get', key);
if (isObject(res)) { // 如果绑定的是对象 继续递归 知道是基本数据为止
return reactive(res);
}
return res;
};
track函数的目的是组成weakmap 对象,把依赖的effect都存起来
// weakmap结构
// {a:1,b:2}:{
// a:[effect],
// b:[effect1,effect2]
//}
const targetMap = new WeakMap(); // 用法与map一致,弱引用 不会内存泄漏
export function track (target, type, key) { //假如 target={a:1,b:2} key=a
if (activeEffect === undefined) {
return; // 说明取值的时候没有effect正在执行 该次取值不在effect中,不依赖于effect
}
let depsMap = targetMap.get(target);// 根据target在WeakMap中取值
if (!depsMap) { // 没有相关依赖说明是第一次绑定 新建依赖map
depsMap = new Map();//{}:{}
targetMap.set(target, depsMap);// targetMap = { {a:1,b:2}:{} }
}
let depsSet = depsMap.get(key); //根据key在map中取值 depsMap里面找key(a)的依赖数组
if (!depsSet) {
depsSet = new Set(); //Set{}
depsMap.set(key, depsSet);// {a:1,b:2}: {a:Set[]}
}
// 执行到某个effect里面获取值的时候,存入当前这个effect,
// 当这个值在其他地方更新的时候 会重新执行这个effect
if (!depsSet.has(activeEffect)) {
// 记录,这个属性改变会触发哪些effect
depsSet.add(activeEffect); // {a:1,b:2}:{key:Set[...oldEffect,effect]}
// 双向记录, effect记录哪些effect需要触发它
activeEffect.deps.push(depsSet); //effect.deps=[Set[...oldEffect,effect]]
}
}
- 当我在effect外部去更改值的时候
- 会触发new Proxy中的 set 拦截 从而触发trigger函数
- trigger函数去查找到改属性对应的weakMap上的 effect 依赖数组
function set (target, key, value, receiver) {
const hasKey = hasOwn(target, key);
const oldValue = target[key]; // Reflect.set 操作前还是历史数据
const res = Reflect.set(target, key, value, receiver); // 更改值并且 返回结果为true / false;
// 新增属性
if (!hasKey) {
console.log('用户修改值', target, key);
// 触发trigger
trigger(target, 'add', key, value);
} else if (value !== oldValue) {
//修改操作
console.log('用户修改值', target, key);
// 触发trigger
trigger(target, 'set', key, value);
}
return res;
};
trigger函数的目的是在weakmap 对象里面找到该属性的值,也就是依赖的effect数组
export function trigger (target, type, key, value, oldValue) {
// {
// {a:1,b:2}:{
// a:[effect],
// b:[effect1,effect2]
// }
// {c:3,d:4}:{
// c:[effect],
// d:[effect1,effect2]
// }
//}
const depsMap = targetMap.get(target); //假如 target={a:1,b:2} key=a
// {a:1,b:2}:{
// a:[effect],
// b:[effect1,effect2]
// }
console.log('target trigger',type,key,target);
if (!depsMap) {
return;
}
if (key !== null) {
const depsSet = depsMap.get(key);// 得到 a:[effect]
if (depsSet) {// [effect]
runEffect(depsSet); // 遍历执行
}
}
}
const runEffect = (effects) => {
effects.forEach(effect => {
effect();
});
};
trigger 要考虑特殊情况
比如数组添加元素等 后续更新
if (type === 'add') {
const isArr = Array.isArray(target);
const depsSet = depsMap.get(isArr ? 'length' : "");
if (depsSet) {
runEffect(depsSet);
}
}
vue3 关于effect的源码部分github