Skip to content

Commit e0003aa

Browse files
authored
fix(runtime-vapor): allow renderEffect to self re-queue on sync state mutation (#14477)
1 parent 57e6da1 commit e0003aa

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

packages/runtime-core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ export { baseEmit, isEmitListener } from './componentEmits'
538538
/**
539539
* @internal
540540
*/
541-
export { queueJob, flushOnAppMount } from './scheduler'
541+
export { queueJob, flushOnAppMount, SchedulerJobFlags } from './scheduler'
542542
/**
543543
* @internal
544544
*/

packages/runtime-vapor/__tests__/componentEmits.spec.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ import {
77
isEmitListener,
88
nextTick,
99
onBeforeUnmount,
10+
ref,
1011
toHandlers,
1112
} from '@vue/runtime-dom'
12-
import { createComponent, defineVaporComponent } from '../src'
13+
import {
14+
createComponent,
15+
createIf,
16+
defineVaporComponent,
17+
template,
18+
} from '../src'
1319
import { makeRender } from './_utils'
1420

1521
const define = makeRender()
@@ -444,4 +450,39 @@ describe('component: emit', () => {
444450

445451
expect(handler).toHaveBeenCalledTimes(1)
446452
})
453+
454+
test('should re-queue when child emit mutates parent state during update', async () => {
455+
const show = ref(false)
456+
const calls: string[] = []
457+
458+
const { component: Child } = define({
459+
emits: ['change'],
460+
setup(_: any, { emit }: any) {
461+
emit('change')
462+
return template('<p>child</p>')()
463+
},
464+
})
465+
466+
const { host } = define({
467+
setup() {
468+
const onChange = () => {
469+
calls.push(`change:${show.value}`)
470+
show.value = false
471+
}
472+
return createIf(
473+
() => show.value,
474+
() =>
475+
createComponent(Child, {
476+
onChange: () => onChange,
477+
}),
478+
)
479+
},
480+
}).render()
481+
482+
show.value = true
483+
await nextTick()
484+
485+
expect(calls).toEqual(['change:true'])
486+
expect(host.innerHTML).toBe('<!--if-->')
487+
})
447488
})

packages/runtime-vapor/src/renderEffect.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EffectFlags, type EffectScope, ReactiveEffect } from '@vue/reactivity'
22
import {
33
type SchedulerJob,
4+
SchedulerJobFlags,
45
currentInstance,
56
queueJob,
67
queuePostFlushCb,
@@ -53,6 +54,12 @@ export class RenderEffect extends ReactiveEffect {
5354

5455
this.job = job
5556
this.i = instance
57+
58+
// Allow self re-queue when render/hook logic mutates reactive state.
59+
// Safe in Vapor because updates are always async via queueJob(), and
60+
// isUpdating prevents duplicate bu/u hooks on re-entry.
61+
this.flags |= EffectFlags.ALLOW_RECURSE
62+
this.job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
5663
}
5764

5865
fn(): void {

0 commit comments

Comments
 (0)