Appearance
effect
- 将函数和数据进行关联,点那个数据发生变化时,所对应的函数会重新执行
js
const data = {
a: 1,
b: 2,
c: 3
}
const state = new Proxy(data, {
get(target, key) {
const result = Reflect.get(target,key)
track(target, key)
return result
},
set(target, key, value) {
const result = Reflect.set(target,key,value)
trigger(target, key, value)
return result
}
})
function track(target, key) {
console.log('track', target, key)
}
function trigger(target, key, value) {
console.log('trigger', target, key, value)
}
function effect(fn) {
fn()
}
effect(() => {
state.a
})
state.a = 111
第一个版本
每一个属性对应
set
集合,包含依赖的函数,属性存在于 map
结构中。由一个变量来控制函数保存
js
const data = {
a: 1,
b: 2,
c: 3
}
// 记录当前的回掉函数
let currentEffect = null
// 对应数据的 key,结构为: {a: Set()}
// 可能一个 key 对应多个 effect 函数
const depsMap = new Map()
const state = new Proxy(data, {
get(target, key) {
const result = Reflect.get(target,key)
track(target, key)
return result
},
set(target, key, value) {
const result = Reflect.set(target,key,value)
trigger(target, key, value)
return result
}
})
function track(target, key) {
// 在进行依赖收集的时候,建立数据与函数的一个关系
if(currentEffect) {
let deps = depsMap.get(key)
if(!deps) {
depsMap.set(key, (deps = new Set()))
}
deps.add(currentEffect)
}
console.log(depsMap)
}
function trigger(target, key, value) {
// 设置的时候将对应关联的函数重新执行
const deps = depsMap.get(key)
if(deps) {
deps.forEach(effect => effect())
}
}
// 记录当前 函数,运行后重置
function effect(fn) {
currentEffect = fn
fn()
currentEffect = null
}
effect(() => {
state.a
})
state.a = 111
// depsMap:
// Map(1) { 'a' => Set(1) { [Function (anonymous)] } }
// Map(1) { 'a' => Set(1) { [Function (anonymous)] } }
第一个版本发现的问题
js
effect(() => {
console.log('effect 函数执行了')
if (state.a === 1) {
return state.b
} else {
return state.c
}
})
state.a = 111
// depsMap:
// effect 函数执行了
// Map(1) { 'a' => Set(1) { [Function (anonymous)] } }
// Map(2) {
// 'a' => Set(1) { [Function (anonymous)] },
// 'b' => Set(1) { [Function (anonymous)] }
// }
// effect 函数执行了
// Map(2) {
// 'a' => Set(1) { [Function (anonymous)] },
// 'b' => Set(1) { [Function (anonymous)] }
// }
// Map(2) {
// 'a' => Set(1) { [Function (anonymous)] },
// 'b' => Set(1) { [Function (anonymous)] }
// }
第二个版本
在修改后依赖错误
问题原因:
第一次建立依赖关系的时候,是将依赖的函数作为 currentEffect。 通过 currentEffect 将依赖函数添加到依赖的 map 中
随后函数执行完 currentEffect 重置为了 null,之后在属性发生变化的时候,重新运行的是回掉函数,但是 currentEffect 依然是 null,所以走不进 track 的判断
解决方式
回掉函数增加环境信息,记录一下这个环境函数在哪个集合中使用
js
function track(target, key) {
if(currentEffect) {
let deps = depsMap.get(key)
if(!deps) {
depsMap.set(key, (deps = new Set()))
}
deps.add(currentEffect)
}
console.log(depsMap)
}
function trigger(target, key, value) {
const deps = depsMap.get(key)
if(deps) {
deps.forEach(effect => effect())
}
}
function effect(fn) {
const environment = () => {
currentEffect = environment
fn()
currentEffect = null
}
environment()
}
// depsMap:
// effect 函数执行了
// Map(1) { 'a' => Set(1) { [Function: environment] } }
// Map(2) {
// 'a' => Set(1) { [Function: environment] },
// 'b' => Set(1) { [Function: environment] }
// }
// effect 函数执行了
// Map(2) {
// 'a' => Set(1) { [Function: environment] },
// 'b' => Set(1) { [Function: environment] }
// }
// Map(3) {
// 'a' => Set(1) { [Function: environment] },
// 'b' => Set(1) { [Function: environment] },
// 'c' => Set(1) { [Function: environment] }
// }
依赖还多了捏 b 应该删除的。
决绝方案: 函数执行前清楚依赖
js
function track(target, key) {
if(currentEffect) {
let deps = depsMap.get(key)
if(!deps) {
depsMap.set(key, (deps = new Set()))
}
currentEffect.deps.push(deps)
deps.add(currentEffect)
}
console.log(depsMap)
}
function cleanup(environment) {
let deps = environment.deps // 当前环境函数的依赖数组
if (deps.length) {
deps.forEach((dep) => {
dep.delete(environment)
if (dep.size === 0) {
for (let [key, value] of depsMap) {
if (value === dep) {
depsMap.delete(key)
}
}
}
})
deps.length = 0
}
}
function trigger(target, key, value) {
const deps = depsMap.get(key)
if(deps) {
deps.forEach(effect => effect())
}
}
function effect(fn) {
const environment = () => {
currentEffect = environment
cleanup() // 清除旧的依赖
fn()
currentEffect = null
}
environment.deps = [] // 记录一下环境函数在哪些集合中使用
environment()
}
// effect 函数执行了
// Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(2) {
// 'a' => Set(1) { [Function: environment] { deps: [Array] } },
// 'b' => Set(1) { [Function: environment] { deps: [Array] } }
// }
// effect 函数执行了
// Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(2) {
// 'a' => Set(1) { [Function: environment] { deps: [Array] } },
// 'c' => Set(1) { [Function: environment] { deps: [Array] } }
// }
第二个版本发现的问题
无限循环
问题原因:
在 track 函数中,每次 state.a 在访问时,都会重新添加当前的 activeEffect 到依赖集合中
在 trigger 函数中,修改 state.a,会触发所有 state.a 的 effect 函数,这些函数又访问了 state.a 导致无限循环
js
effect(() =>{
if(state.a === 1) {
state.b
}else{
state.c
}
})
effect(() => {
state.a
state.c
})
state.a = 111
解决方案:
js
function trigger(target, key, value) {
// 设置的时候将对应关联的函数重新执行
const deps = depsMap.get(key)
if(deps) {
// 复制一份,避免循环引用
const effectToRun = new Set(deps)
effectToRun.forEach(effect => effect())
}
}
// Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(2) {
// 'a' => Set(1) { [Function: environment] { deps: [Array] } },
// 'b' => Set(1) { [Function: environment] { deps: [Array] } }
// }
// Map(2) {
// 'a' => Set(2) {
// [Function: environment] { deps: [Array] },
// [Function: environment] { deps: [Array] }
// },
// 'b' => Set(1) { [Function: environment] { deps: [Array] } }
// }
// Map(3) {
// 'a' => Set(2) {
// [Function: environment] { deps: [Array] },
// [Function: environment] { deps: [Array] }
// },
// 'b' => Set(1) { [Function: environment] { deps: [Array] } },
// 'c' => Set(1) { [Function: environment] { deps: [Array] } }
// }
第三个版本
js
effect(() => {
effect(() =>{
effect(() => {
state.c
})
})
state.b
state.a
})
// Map(1) { 'c' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(1) { 'c' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(1) { 'c' => Set(1) { [Function: environment] { deps: [Array] } } }
问题原因:
问题原因是 执行到effect到时候,给 currentEffect = null
解决方案: 模拟入栈出栈
js
const effectStack = []
const environment = () => {
currentEffect = environment
effectStack.push(environment)
cleanup(environment)
fn()
effectStack.pop()
currentEffect = effectStack[effectStack.length-1]
// currentEffect = null
}
environment.deps = []
environment()
}
// Map(1) { 'c' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(2) {
// 'c' => Set(1) { [Function: environment] { deps: [Array] } },
// 'b' => Set(1) { [Function: environment] { deps: [Array] } }
// }
// Map(3) {
// 'c' => Set(1) { [Function: environment] { deps: [Array] } },
// 'b' => Set(1) { [Function: environment] { deps: [Array] } },
// 'a' => Set(1) { [Function: environment] { deps: [Array] } }
// }