第一步 上面的依赖收集中,会有这么个问题,如下:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <div id ="app" > </div > <script type ="module" > import { reactive, effect } from './reactivity.js' ; let obj = { name : 'xiaoming' , age : 18 , flag : true }; const state = reactive (obj); effect (() => { app.innerHTML = state.flag ? state.name : state.age ; }) setTimeout (() => { state.flag = false ; setTimeout (() => { state.name = 'xiaosheng' ; }) }, 1000 ); </script > </body > </html >
上面的代码中,当 flag 的值是 true 的时候,收集的是 name 这个属性,但是,当在定时器中将修改了 flag 的值后,收集了 age 这个属性,同时,这个时候,name 这个属性还被收集着,然后在定时器中修改了 name 的值之后,这个时候就不应该在收集了才对,所以需要做一下依赖清理,清理是使用了一个简单的 diff 算法来处理的
每次在收集依赖之前,将上一次的依赖给清空了
effect.ts
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 ... function preCleanEffect (effect ) { effect._depsLength = 0 ; effect._trackId ++; } class ReactiveEffect { _trackId = 0 ; deps = []; _depsLength = 0 ; ... run ( ) { ... try { activeEffect = this ; preCleanEffect (this ); return this .fn (); } finally { activeEffect = lastEffect; } } } ...
第二步 重新收集依赖,现在如果同一个属性对应同一个 effect 的话,会收集多次,应该是只收集一次,但是 effect 中的 _trackId 递增才对,即针对这种情况做处理,下面这种情况,name 属性应该只收集一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <div id ="app" > </div > <script type ="module" > import { reactive, effect } from './reactivity.js' ; let obj = { name : 'xiaoming' , age : 18 , flag : true }; const state = reactive (obj); effect (() => { app.innerHTML = state.name + state.name + state.name ; }) </script > </body > </html >
effect.ts
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 ... function postCleanEffect (effect ) { if (effect.deps .length > effect._depsLength ) { for (let i = effect._depsLength ; i < effect.deps .length ; i++) { cleanDepEffect (effect.deps [i], effect); } effect.deps .length = effect._depsLength ; } } function cleanDepEffect (dep, effect ) { dep.delete (effect); if (dep.size === 0 ) { dep.cleanup (); } } export function trackEffect (effect, dep ) { if (dep.get (effect) !== effect._trackId ) { dep.set (effect, effect._trackId ); const oldDep = effect.deps [effect._depsLength ]; if (oldDep !== dep) { if (oldDep) { cleanDepEffect (oldDep, effect); } effect.deps [effect._depsLength ++] = dep; } else { effect._depsLength ++; } } } ...