watch的本质就是观测一个响应式数据,当数据发生变化时通知并执行相应的回调函数,利用了effect和options.scheduler选项,利用副作用函数重新执行时的可调度性,一个watch本身会创建一个effect,当这个effect依赖的响应式数据变化时,会执行该effect的调度函数,即scheduler,这里的scheduler可以认为是“回调”,所以我们只需要在scheduler中执行用户通过watch注册的回调函数即可
比如以下例子:
1 2 3 4 5 6
| const data={foo:1} const obj = new Proxy(data,{}) watch(obj,()=>{ console.log("数据变化了") }) obj.foo++
|
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
|
function watch(source, cb) { let getter; if (typeof source === "function") { getter = source; } else { getter = () => traverse(source); } let oldValue, newValue; const effectfn = effect( () => getter, { lazy: true, scheduler() { newValue = effectfn(); cb(newValue, oldValue); oldValue = newValue; }, } ); oldValue = effectfn(); }
function traverse(value, seen = new Set()) { if (typeof value !== "object" || value === null || seen.has(value)) return; seen.add(value); for (let k in value) { traverse(value[k], seen); } }
|
如何拿到新值和旧值:lazy选项创建了一个懒执行的effect,最下面部分我们手动调用effectFn函数得到的返回值就是旧值,即第一次执行得到的值,当变化发生触发scheduler调度函数执行时,会重新调用effectFn函数并得到新值,这样我们总可以拿到旧值和新值,接着把它们传递给回调函数cb即可,再用新值更新旧值
立即执行的watch和回调执行时机:
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
| function watch(source, cb,options={}) { let getter; if (typeof source === "function") { getter = source; } else { getter = () => traverse(source); } let oldValue, newValue; const job = () => { newValue=effectfn() cb(newValue, oldValue); oldValue = newValue; }
const effectfn = effect( () => getter, { lazy: true, scheduler: ()=>{ if(options.flush==='post'){ const p= Promise.resolve() p.then(job) }else{ job() } } } ); if(options.immediate){ job() }else{ oldValue = effectfn(); } }
|
由于回调函数时立即执行,所以第一次回调执行时没有旧值,因此此时回调函数的oldValue值为undefined
过期的副作用:
watch回调函数接收第三个参数onInvalidate,它是一个函数,类似于事件监听器,我们可以使用onInvalidate函数注册一个回调,这个回调函数会在当前副作用函数过期时执行
使用例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| watch(obj,async(newValue,oldValue,onInvalidate) => { let expired = false onInvalidate(()=>{ expired=true }) const res=await fetch('path/to/request') if(!expired){ finalData=res } })
obj.foo++; setTimeout(()=>{ obj.foo++ },200)
|
watch处理过期回调:
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
| function watch(source, cb,options={}) { let getter; if (typeof source === "function") { getter = source; } else { getter = () => traverse(source); } let oldValue, newValue; let cleanup function onInvalidate(fn){ cleanup=fn } const job = () => { newValue=effectfn() if(cleanup){ cleanup() } cb(newValue, oldValue,onInvalidate); oldValue = newValue; }
const effectfn = effect( () => getter, { lazy: true, scheduler: ()=>{ if(options.flush==='post'){ const p= Promise.resolve() p.then(job) }else{ job() } } } ); if(options.immediate){ job() }else{ oldValue = effectfn(); } }
|
第一次修改obj.foo,立即执行,watch回调函数调用onInvalidata,注册过期回调,接着A请求,加入1000ms返回结果,我们在200ms后第二次修改obj,foo,又会导致watch回调函数执行,会执行过期回调,将expired设为true,则请求A的结果返回将被抛弃,避免过期副作用回调函数带来的影响