Skip to content

Commit 995d76b

Browse files
authored
fix(runtime-core): fix render function + optimized slot edge case (#3523)
fix #2893 Manually rendering the optimized slots should allow subsequent updates to exit the optimization mode correctly
1 parent c90fb94 commit 995d76b

File tree

3 files changed

+71
-4
lines changed

3 files changed

+71
-4
lines changed

packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts

+60-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import {
1515
defineComponent,
1616
withCtx,
1717
renderSlot,
18-
onBeforeUnmount
18+
onBeforeUnmount,
19+
createTextVNode,
20+
SetupContext,
21+
createApp
1922
} from '@vue/runtime-test'
2023
import { PatchFlags, SlotFlags } from '@vue/shared'
2124

@@ -517,4 +520,60 @@ describe('renderer: optimized mode', () => {
517520
expect(spyA).toHaveBeenCalledTimes(1)
518521
expect(spyB).toHaveBeenCalledTimes(1)
519522
})
523+
524+
// #2893
525+
test('manually rendering the optimized slots should allow subsequent updates to exit the optimized mode correctly', async () => {
526+
const state = ref(0)
527+
528+
const CompA = {
529+
setup(props: any, { slots }: SetupContext) {
530+
return () => {
531+
return (
532+
openBlock(),
533+
createBlock('div', null, [renderSlot(slots, 'default')])
534+
)
535+
}
536+
}
537+
}
538+
539+
const Wrapper = {
540+
setup(props: any, { slots }: SetupContext) {
541+
// use the manually written render function to rendering the optimized slots,
542+
// which should make subsequent updates exit the optimized mode correctly
543+
return () => {
544+
return slots.default!()[state.value]
545+
}
546+
}
547+
}
548+
549+
const app = createApp({
550+
setup() {
551+
return () => {
552+
return (
553+
openBlock(),
554+
createBlock(Wrapper, null, {
555+
default: withCtx(() => [
556+
createVNode(CompA, null, {
557+
default: withCtx(() => [createTextVNode('Hello')]),
558+
_: 1 /* STABLE */
559+
}),
560+
createVNode(CompA, null, {
561+
default: withCtx(() => [createTextVNode('World')]),
562+
_: 1 /* STABLE */
563+
})
564+
]),
565+
_: 1 /* STABLE */
566+
})
567+
)
568+
}
569+
}
570+
})
571+
572+
app.mount(root)
573+
expect(inner(root)).toBe('<div>Hello</div>')
574+
575+
state.value = 1
576+
await nextTick()
577+
expect(inner(root)).toBe('<div>World</div>')
578+
})
520579
})

packages/runtime-core/src/componentSlots.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ export const initSlots = (
130130

131131
export const updateSlots = (
132132
instance: ComponentInternalInstance,
133-
children: VNodeNormalizedChildren
133+
children: VNodeNormalizedChildren,
134+
optimized: boolean
134135
) => {
135136
const { vnode, slots } = instance
136137
let needDeletionCheck = true
@@ -143,14 +144,21 @@ export const updateSlots = (
143144
// Parent was HMR updated so slot content may have changed.
144145
// force update slots and mark instance for hmr as well
145146
extend(slots, children as Slots)
146-
} else if (type === SlotFlags.STABLE) {
147+
} else if (optimized && type === SlotFlags.STABLE) {
147148
// compiled AND stable.
148149
// no need to update, and skip stale slots removal.
149150
needDeletionCheck = false
150151
} else {
151152
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
152153
// normalization.
153154
extend(slots, children as Slots)
155+
// #2893
156+
// when rendering the optimized slots by manually written render function,
157+
// we need to delete the `slots._` flag if necessary to make subsequent updates reliable,
158+
// i.e. let the `renderSlot` create the bailed Fragment
159+
if (!optimized && type === SlotFlags.STABLE) {
160+
delete slots._
161+
}
154162
}
155163
} else {
156164
needDeletionCheck = !(children as RawSlots).$stable

packages/runtime-core/src/renderer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1576,7 +1576,7 @@ function baseCreateRenderer(
15761576
instance.vnode = nextVNode
15771577
instance.next = null
15781578
updateProps(instance, nextVNode.props, prevProps, optimized)
1579-
updateSlots(instance, nextVNode.children)
1579+
updateSlots(instance, nextVNode.children, optimized)
15801580

15811581
pauseTracking()
15821582
// props update may have triggered pre-flush watchers.

0 commit comments

Comments
 (0)