Procházet zdrojové kódy

fix(runtime-vapor): allow renderEffect to self re-queue on sync state mutation (#14477)

edison před 1 měsícem
rodič
revize
e0003aab5d

+ 1 - 1
packages/runtime-core/src/index.ts

@@ -538,7 +538,7 @@ export { baseEmit, isEmitListener } from './componentEmits'
 /**
  * @internal
  */
-export { queueJob, flushOnAppMount } from './scheduler'
+export { queueJob, flushOnAppMount, SchedulerJobFlags } from './scheduler'
 /**
  * @internal
  */

+ 42 - 1
packages/runtime-vapor/__tests__/componentEmits.spec.ts

@@ -7,9 +7,15 @@ import {
   isEmitListener,
   nextTick,
   onBeforeUnmount,
+  ref,
   toHandlers,
 } from '@vue/runtime-dom'
-import { createComponent, defineVaporComponent } from '../src'
+import {
+  createComponent,
+  createIf,
+  defineVaporComponent,
+  template,
+} from '../src'
 import { makeRender } from './_utils'
 
 const define = makeRender()
@@ -444,4 +450,39 @@ describe('component: emit', () => {
 
     expect(handler).toHaveBeenCalledTimes(1)
   })
+
+  test('should re-queue when child emit mutates parent state during update', async () => {
+    const show = ref(false)
+    const calls: string[] = []
+
+    const { component: Child } = define({
+      emits: ['change'],
+      setup(_: any, { emit }: any) {
+        emit('change')
+        return template('<p>child</p>')()
+      },
+    })
+
+    const { host } = define({
+      setup() {
+        const onChange = () => {
+          calls.push(`change:${show.value}`)
+          show.value = false
+        }
+        return createIf(
+          () => show.value,
+          () =>
+            createComponent(Child, {
+              onChange: () => onChange,
+            }),
+        )
+      },
+    }).render()
+
+    show.value = true
+    await nextTick()
+
+    expect(calls).toEqual(['change:true'])
+    expect(host.innerHTML).toBe('<!--if-->')
+  })
 })

+ 7 - 0
packages/runtime-vapor/src/renderEffect.ts

@@ -1,6 +1,7 @@
 import { EffectFlags, type EffectScope, ReactiveEffect } from '@vue/reactivity'
 import {
   type SchedulerJob,
+  SchedulerJobFlags,
   currentInstance,
   queueJob,
   queuePostFlushCb,
@@ -53,6 +54,12 @@ export class RenderEffect extends ReactiveEffect {
 
     this.job = job
     this.i = instance
+
+    // Allow self re-queue when render/hook logic mutates reactive state.
+    // Safe in Vapor because updates are always async via queueJob(), and
+    // isUpdating prevents duplicate bu/u hooks on re-entry.
+    this.flags |= EffectFlags.ALLOW_RECURSE
+    this.job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
   }
 
   fn(): void {