فهرست منبع

perf(runtime-vapor): only create lifecycle update jobs when needed (#14824)

edison 1 ماه پیش
والد
کامیت
0efdb819b4
2فایلهای تغییر یافته به همراه92 افزوده شده و 13 حذف شده
  1. 77 0
      packages/runtime-vapor/__tests__/renderEffect.spec.ts
  2. 15 13
      packages/runtime-vapor/src/renderEffect.ts

+ 77 - 0
packages/runtime-vapor/__tests__/renderEffect.spec.ts

@@ -12,6 +12,7 @@ import {
   watchSyncEffect,
 } from '@vue/runtime-dom'
 import { renderEffect, template } from '../src'
+import { RenderEffect } from '../src/renderEffect'
 import { onEffectCleanup } from '@vue/reactivity'
 import { makeRender } from './_utils'
 
@@ -34,6 +35,82 @@ const createDemo = (setupFn: () => any, renderFn: (ctx: any) => any) =>
   })
 
 describe('renderEffect', () => {
+  test('initializes noLifecycle effect with raw effect function', () => {
+    let calls = 0
+    const fn = () => {
+      calls++
+    }
+    const effect = new RenderEffect(fn, true)
+
+    expect(effect.fn).toBe(fn)
+    expect(effect.updateJob).toBe(undefined)
+
+    effect.run()
+    expect(calls).toBe(1)
+  })
+
+  test('creates update lifecycle job lazily', async () => {
+    const effect = new RenderEffect(() => {})
+    expect(effect.updateJob).toBe(undefined)
+
+    const effects: RenderEffect[] = []
+    const calls: string[] = []
+    const { instance } = createDemo(
+      () => {
+        const source = ref(0)
+        const update = () => source.value++
+        onUpdated(() => calls.push(`updated ${source.value}`))
+        return { source, update }
+      },
+      ctx => {
+        const effect = new RenderEffect(() => {
+          calls.push(`render ${ctx.source}`)
+        })
+        effects.push(effect)
+        effect.run()
+      },
+    ).render()
+
+    expect(effects[0].updateJob).toBe(undefined)
+    expect(calls).toEqual(['render 0'])
+
+    const { update } = instance?.setupState as any
+    update()
+    await nextTick()
+
+    expect(effects[0].updateJob).toEqual(expect.any(Function))
+    expect(calls).toEqual(['render 0', 'render 1', 'updated 1'])
+  })
+
+  test('creates update lifecycle job after hooks are registered late', async () => {
+    const effects: RenderEffect[] = []
+    const calls: string[] = []
+    const { instance } = createDemo(
+      () => {
+        const source = ref(0)
+        const update = () => source.value++
+        const effect = new RenderEffect(() => {
+          calls.push(`render ${source.value}`)
+        })
+        effects.push(effect)
+        effect.run()
+        onUpdated(() => calls.push(`updated ${source.value}`))
+        return { update }
+      },
+      () => {},
+    ).render()
+
+    expect(effects[0].updateJob).toBe(undefined)
+    expect(calls).toEqual(['render 0'])
+
+    const { update } = instance?.setupState as any
+    update()
+    await nextTick()
+
+    expect(effects[0].updateJob).toEqual(expect.any(Function))
+    expect(calls).toEqual(['render 0', 'render 1', 'updated 1'])
+  })
+
   test('basic', async () => {
     let dummy: any
     const source = ref(0)

+ 15 - 13
packages/runtime-vapor/src/renderEffect.ts

@@ -17,10 +17,12 @@ import { invokeArrayFns } from '@vue/shared'
 export class RenderEffect extends ReactiveEffect {
   i: VaporComponentInstance | null
   job: SchedulerJob
-  updateJob: SchedulerJob
+  updateJob?: SchedulerJob
+  render: () => void
 
-  constructor(public render: () => void) {
-    super()
+  constructor(render: () => void, noLifecycle = false) {
+    super(noLifecycle ? render : undefined)
+    this.render = render
     const instance = currentInstance as VaporComponentInstance | null
     if (__DEV__ && !__TEST__ && !this.subs && !isVaporComponent(instance)) {
       warn('renderEffect called without active EffectScope or Vapor instance.')
@@ -31,13 +33,9 @@ export class RenderEffect extends ReactiveEffect {
         this.run()
       }
     }
-    this.updateJob = () => {
-      instance!.isUpdating = false
-      instance!.u && invokeArrayFns(instance!.u)
-    }
 
     if (instance) {
-      if (__DEV__) {
+      if (__DEV__ && !noLifecycle) {
         this.onTrack = instance.rtc
           ? e => invokeArrayFns(instance.rtc!, e)
           : void 0
@@ -83,7 +81,14 @@ export class RenderEffect extends ReactiveEffect {
           instance.isUpdating = false
           throw err
         }
-        queuePostFlushCb(this.updateJob)
+        let updateJob = this.updateJob
+        if (!updateJob) {
+          updateJob = this.updateJob = () => {
+            instance.isUpdating = false
+            instance.u && invokeArrayFns(instance.u)
+          }
+        }
+        queuePostFlushCb(updateJob)
       } else {
         this.render()
       }
@@ -107,9 +112,6 @@ export function renderEffect(fn: () => void, noLifecycle = false): void {
   // in once slot, just run the function directly
   if (inOnceSlot) return fn()
 
-  const effect = new RenderEffect(fn)
-  if (noLifecycle) {
-    effect.fn = fn
-  }
+  const effect = new RenderEffect(fn, noLifecycle)
   effect.run()
 }