前言 上篇回顾,核心代码逻辑是通过 reactive 中的 Proxy()来代理一个对象,然后通过 get 收集依赖,主要操作放在来 effect 中。那么当我们回顾上一篇的问题。当用户有一个这样当操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const { effect, reactive } = VueReactivity ;let target = { name : "david" , age : 12 , address : { num : 567 }, flag : true };const state = reactive (target);effect (() => { console .log ("render" ); document .getElementById ("app" ).innerHTML = state.flag ? "姓名:" + state.name : "年龄:" + state.age ; }); setTimeout (() => { state.flag = false ; setTimeout (() => { console .log ("修改了name,原则不重新渲染" ); state.name = "jack" ; }, 1000 ); }, 1000 );
第一次,执行来用户的渲染操作,然后在之后的操作中修改来 flag。这个时候,依赖收集的应该是 flag 和 name,如果采用上篇中的代码,那么实际上,旧的 name 依赖未被清除,还是会留在 deps 中,那么你修改 name 的时候会触发渲染。
effect 分支删除 上面的问题已经很清晰来,那么如果解决呢。可以在用户函数执行之前,把旧的依赖全部清空,再收集一次这个依赖不就行了。这样第一次收集了 flag,name 依赖。第二次 flag 变成 flase,清空依赖,收集 flag 和 age 以来,这样第三次修改 name 值的时候就不会触发渲染了。 前一篇中定义的 deps 也派上了用场,由于之前做了双向收集,那么在执行用户操作之前,清空依赖就行了。 定义一个clearupEffect()
函数。
1 2 3 4 5 6 7 function clearupEffect (effect: ReactiveEffect ) { let { deps } = effect; for (let i = 0 ; i < deps.length ; i++) { deps[i].delete (effect); } effect.deps .length = 0 ; }
放入到 this.fn()
被执行之前,来清除依赖。但是这里会有个新问题,看代码。
这里执行了清空,下面执行了this.fn()
又会触发渲染,然后由于使用的是Set()
来存储关系的, Set()
一边清空一边添加依赖,导致了死循环,会一直触发渲染。为此依赖触发的方法要进行修改,我们拷贝一份Set()
,然后在他的基础上删除清空,这样就不会造成死循环了。
1 2 3 4 5 6 7 8 9 10 11 if (effects) { effects = new Set (effects); effects.forEach ((effect ) => { if (activeEffect !== effect) effect.run (); }); }
完整的 effect 代码 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 export let activeEffect = undefined ;function clearupEffect (effect : ReactiveEffect ) { let { deps } = effect; for (let i = 0 ; i < deps.length ; i++) { deps[i].delete (effect); } effect.deps .length = 0 ; } class ReactiveEffect { public active = true ; public parent = null ; public deps = []; constructor (public fn ) {} run ( ) { if (!this .active ) { this .fn (); } try { this .parent = activeEffect; activeEffect = this ; clearupEffect (this ); return this .fn (); } finally { activeEffect = this .parent ; } } } export function effect (fn ) { const _effect = new ReactiveEffect (fn); _effect.run (); } let targetMap = new WeakMap ();export function track (target, type , key ) { if (!activeEffect) return ; 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) { dep.add (activeEffect); activeEffect.deps .push (dep); } } export function trigger (target, type , key, value, oldValue ) { const depsMap = targetMap.get (target); if (!depsMap) return ; let effects = depsMap.get (key); if (effects) { effects = new Set (effects); effects.forEach ((effect ) => { if (activeEffect !== effect) effect.run (); }); } }
1 git:[@github/MicroMatrixOrg/vue3-plan/tree/effect_schedule]