一个响应式系统:
- 当读取操作发生时,将副作用函数收集到桶里
- 当设置操作发生时,从桶里取出副作用函数执行
为了让副作用函数无论是什么形式都能被收集到桶里,设置一个affectEffect全局变量来存储被注册的副作用函数
1 2 3 4 5 6 7 8
| let activeEffect;
let effectStack = []; function effect(fn, options = {}) { activeEffect = fn; fn() }
|
分支切换时清除遗留副作用函数
1 2 3 4
| const data={ok:true,text:'hello'} effect(function effectFn(){ document.body.innerText=obj.ok?obj.text:'not' })
|
当obj.ok改为false时,此时obj.text不会被读取,只会触发obj.ok的读取操作,理想情况下副作用函数effectFn不应该被字段obj.text所对应的依赖集合收集,然而,整个依赖关系仍然保持,则单修改obj.text会重新执行副作用函数,这是不应该的,解决这个问题,需要在每次副作用函数执行时,把它从所有与之关联的依赖集合中删除:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| let activeEffect function effect(fn){ const effectFn=()=>{ activeEffect=effectFn fn() } effectFn.deps=[] } const bucket = new WeakMap();
function track(target, key) { if (!activeEffect || !shouldTrack) return; let depsMap = bucket.get(target); if (!depsMap) { bucket.set(target, (depsMap = new Map())); } let deps = depsMap.get(key); if (!deps) { depsMap.set(key, (deps = new Set())); } deps.add(activeEffect); activeEffect.deps.push(deps); }
function cleanup(effectfn) { for (let i = 0; i < effectfn.deps.length; i++) { let deps = effectfn.deps[i]; deps.delete(effectfn); } effect.deps.length = 0; }
|
解决无限循环
但是这样会引起无限循环,在trigger函数中,我们遍历effects集合,执行副作用函数,当副作用函数执行,cleanup清除,就是从effects集合中将当前执行的副作用函数删除,但是副作用函数的执行又会导致其重新被收集到集合中华,造成无限循环,可以构造另外一个Set集合并遍历它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function trigger(target, key) { const depsMap = bucket.get(target); if (!depsMap) return; const effects = depsMap.get(key); const effectsToRun = new Set(effects); effects && effects.forEach((effectfn) => { if (activeEffect != effectfn) { effectsToRun.add(effectfn); } }); effectsToRun.forEach((effectfn) => effectfn()); }
|
嵌套effect与effect栈:
当在组件Foo中渲染另一个组件,会发生effect嵌套:
1 2 3 4 5 6 7 8
| const Bar={ render(){} } const Foo={ render(){ return <Bar/> } }
|
此时就发生了effect嵌套:
1 2 3 4 5 6 7
| effect(()=>{ Foo.render() effect(()=>{ Bar.render() }) })
|
然而,如果只是用activeEffect来存储通过effect函数注册的副作用函数,这意味着任意时刻activeEffect所存储的副作用函数只有一个,当副作用函数发生嵌套时,内层副作用函数的执行会覆盖activeEffect的值,并且永远不会恢复到原来的值,为了解决这个问题,我们需要一个副作用函数栈effectStack,在副作用函数执行时,将当前副作用函数入栈,待副作用函数执行完毕将其从栈中弹出,并始终让activeEffect指向栈顶的副作用函数,这样就能做到一个响应式数据只会收集直接读取其值的副作用函数,而不会出现互相影响的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| let activeEffect;
let effectStack = [];
function effect(fn, options = {}) { const effectfn = () => { cleanup(effectfn); effectStack.push(effectfn); activeEffect = effectfn; const res = fn(); effectStack.pop(); activeEffect = effectStack[effectStack.length - 1]; return res; }; effectfn.deps = []; if (!options.lazy) { effectfn(); } return effectfn; }
|