48-mini-vue 实现 watchEffect

实现 watchEffect

  1. 测试
import{reactive}from"@guide-mini-vue/reactivity"import{nextTick}from"../src/scheduler"import{expect}from"vitest"import{watchEffect}from'../src/apiWatch'describe('api: watch',()=>{it("effect",async()=>{conststate=reactive({count:0})letdummywatchEffect(()=>{dummy=state.count})expect(dummy).toBe(0)state.count++awaitnextTick()expect(dummy).toBe(1)})})
  1. 功能点
  • 经过 watchEffect 包裹的 fn,在初始化时会先执行一遍 fn
  • 组件更新渲染前,调用包裹的 fn 函数
  1. 实现
// renderer.tsfunctionsetupRenderEffect(instance,vnode,container,anchor){instance.update=effect(()=>{let{proxy}=instanceif(!instance.isMounted){constsubTree=instance.subTree=instance.render.call(proxy,proxy)patch(null,subTree,container,instance,anchor)vnode.el=subTree.el instance.isMounted=true}else{const{next,vnode}=instanceif(next){next.el=vnode.elupdateComponentPreRender(instance,next)}const{proxy}=instanceconstsubTree=instance.render.call(proxy,proxy)constprevSubTree=instance.subTree instance.subTree=subTreepatch(prevSubTree,subTree,container,instance,anchor)}},{scheduler(){// ✅ 注意:这里是相关位置,虽然我们这块没有改动queueJobs(instance.update)}})}return{createApp:createAppAPI(render)}// scheduler.tsletqueue:any[]=[]constactivePreFlushCbs:any[]=[]// ✅letisFlushPending=falseconstp=Promise.resolve()exportfunctionnextTick(fn?:any){returnfn?p.then(fn):p}exportfunctionqueueJobs(job){if(!queue.includes(job)){queue.push(job)}queueFlush()}functionqueueFlush(){if(isFlushPending)returnisFlushPending=truenextTick(flushJobs)}exportfunctionqueuePreFlushCb(cb){// ✅ 收集 watchEffect 依赖activePreFlushCbs.push(cb)flushJobs()}functionflushJobs(){isFlushPending=false// 组件渲染之前flushPreFlushCbs()// ✅ 执行 watchEffect 收集依赖// 组件渲染letjobwhile(job=queue.shift()){job&&job()}}functionflushPreFlushCbs(){// ✅for(leti=0;i<activePreFlushCbs.length;i++){activePreFlushCbs[i]()}}
  1. 功能点
// apiWatch.spec.tsit("stopping the watcher (effect)",async()=>{conststate=reactive({count:0})letdummyconststop:any=watchEffect(()=>{dummy=state.count})expect(dummy).toBe(0)stop()state.count++awaitnextTick()expect(dummy).toBe(0)})
  1. 实现
// ✅ apiWatch.tsimport{ReactiveEffect}from"../../reactivity/src/effect";import{queuePreFlushCb}from'./scheduler'exportfunctionwatchEffect(fn:any){// 执行fn,需要放到组件渲染之前functionjob(){effect.run()}consteffect=newReactiveEffect(fn,()=>{queuePreFlushCb(job)})effect.run()}
  1. 功能点
// apiWatch.spec.tsit("stopping the watcher (effect)",async()=>{conststate=reactive({count:0})letdummyconststop:any=watchEffect(()=>{dummy=state.count})expect(dummy).toBe(0)stop()state.count++awaitnextTick()expect(dummy).toBe(0)})
  1. 实现
import{ReactiveEffect}from"../../reactivity/src/effect";import{queuePreFlushCb}from'./scheduler'exportfunctionwatchEffect(fn:any){// 执行fn,需要放到组件渲染之前functionjob(){effect.run()}consteffect=newReactiveEffect(fn,()=>{queuePreFlushCb(job)})effect.run()return()=>{// ✅effect.stop()}}
  1. 功能点
// apiWatch.spec.tsit("cleanup registration (effect)",async()=>{conststate=reactive({count:0})constcleanup=vi.fn()letdummyconststop:any=watchEffect((onCleanup)=>{onCleanup(cleanup)dummy=state.count})expect(dummy).toBe(0)state.count++awaitnextTick()expect(cleanup).toHaveBeenCalledTimes(1)expect(dummy).toBe(1)// stop()// expect(cleanup).toHaveBeenCalledTimes(2)})
  1. 实现
// apiWatch.tsimport{ReactiveEffect}from"../../reactivity/src/effect";import{queuePreFlushCb}from'./scheduler'exportfunctionwatchEffect(source){// 执行fn,需要放到组件渲染之前functionjob(){effect.run()}letcleanup;// ✅constonCleanup=(fn)=>{// ✅cleanup=fn}functiongetter(){// ✅if(cleanup){cleanup()}source(onCleanup)}consteffect=newReactiveEffect(getter,()=>{queuePreFlushCb(job)})effect.run()return()=>{effect.stop()}}
  1. 功能点
// 实现 spec 中的注释点stop()expect(cleanup).toHaveBeenCalledTimes(2)// watchEffect.tsimport{ReactiveEffect}from"../../reactivity/src/effect";import{queuePreFlushCb}from'./scheduler'exportfunctionwatchEffect(source){// 执行fn,需要放到组件渲染之前functionjob(){effect.run()}letcleanup;constonCleanup=(fn)=>{// cleanup = fn// effect.onStop = () => { // ✅// fn()// }// ✅ 上面可以优化为cleanup=effect.onStop=()=>{// ✅fn()}}functiongetter(){if(cleanup){cleanup()}source(onCleanup)}consteffect=newReactiveEffect(getter,()=>{queuePreFlushCb(job)})effect.run()return()=>{effect.stop()}}