effect 实现
需要考虑的场景
1.用 标识,在effect嵌套,多个并排effect的格式下判断是否为 effect内部的代理对象的变量
2.用 映射表,双向标记,完成依赖收集,解决 属性 和 effect 的 多对多的关系
实现结果
export function effect(fn) {// 副作用函数
const _effect = new ReactiveEffect(fn)
_effect.run() // 默认让响应式的effect执行
}
// 核心类
let activeEffect;
class ReactiveEffect {
public active = true;
public deps = [];//依赖收集项
public parent = undefined;
constructor(public fn) {}
run() {
if (!this.active) {
return this.fn(); // 直接执行此函数即可
}
// 其他情况下 意味着是激活的状态
// 1.针对 effect 嵌套的格式,使用标记判断是否为 effect内部的代理对象的变量
try { // tree树父子关系
this.parent = activeEffect;
activeEffect = this;
return this.fn() // 会取响应式的数据
} finally { // 方法执行完 去掉标记
activeEffect = this.parent
this.parent = undefined
}
// 2.对于 多个effect并排的情况,要方便区别变量是否在effect内部
}
}
// 映射表
const targetMap = new WeakMap()
export function track(target, key) {
if (!activeEffect) {
// 取值操作没有发生在effect中
return;
}
debugger
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if(!dep){
depsMap.set(key,(dep = new Set()))
}
let shouldTrack = !dep.has(activeEffect)
if(shouldTrack){
// 双向的标记
// 一个属性可能对应多个 effect,一个effect 可能对应多个属性
// 属性 和 effect 的关系时多对多
dep.add(activeEffect);
activeEffect.deps.push(dep);//依赖收集
}
}
// 在getter 中触发 track 依赖收集 处理
get(target, key, receiver) {// 目标 键名 当前代理对象
if (ReactiveFlags.IS_REACTIVE == key) {
// 第一次 target[ReactiveFlags.IS_REACTIVE]时,未触发getter,当代理已代理对象,会触发已代理的 getter
return true
}
track(target,key)
return Reflect.get(target, key, receiver) // 处理了 this 问题
},
场景1
在effect嵌套格式
const state = reactive({name: 'jw', age: 30});
effect(() => {
app.innerHTML = state.name + state.age
effect(() => {
app.innerHTML = state.name + state.age
})
})
多个并排effect的格式
effect(() => {
document.getElementById('app').innerHTML = state.name + state.age
})
effect(() => {
document.getElementById('app').innerHTML = state.name + state.age
})
解决方案: parent 标识
// 1.针对 effect 嵌套的格式和多个effect并排的情况
// 使用 activeEffect 标记,判断是否为 effect内部的回调
try { // 方法执行时 加上 标记 为 当前 实例 类
this.parent = activeEffect;
activeEffect = this;
return this.fn() // 会取响应式的数据
} finally { // 方法执行完,若在最外层则去掉标记
activeEffect = this.parent
this.parent = undefined
}
...
if (!activeEffect) {
// 取值操作没有发生在effect中
return;
}
场景2
属性 和 effect 的 多对多的关系
// 一个 effect 对 name,age 属性
effect(() => {
app.innerHTML = state.name + state.age
})
// 多个 effect 对 name 属性
effect(() => {
document.getElementById('app').innerHTML = state.name
})
effect(() => {
document.getElementById('app').innerHTML = state.name
})
解决方案:映射表
//let mapping = { // 映射表格式
// target: {
// key: [activeEffect]
// }
// }
// 映射表
const targetMap = new WeakMap()
export function track(target, key) {
if (!activeEffect) {
// 取值操作没有发生在effect中
return;
}
debugger
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
let shouldTrack = !dep.has(activeEffect)
if (shouldTrack) {
// 双向的标记
// 一个属性可能对应多个 effect,一个effect 可能对应多个属性
// 属性 和 effect 的关系时多对多
dep.add(activeEffect);
activeEffect.deps.push(dep);//依赖收集
}
}