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
This commit is contained in:
parent
c90fb945f4
commit
995d76bd12
@ -15,7 +15,10 @@ import {
|
|||||||
defineComponent,
|
defineComponent,
|
||||||
withCtx,
|
withCtx,
|
||||||
renderSlot,
|
renderSlot,
|
||||||
onBeforeUnmount
|
onBeforeUnmount,
|
||||||
|
createTextVNode,
|
||||||
|
SetupContext,
|
||||||
|
createApp
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { PatchFlags, SlotFlags } from '@vue/shared'
|
import { PatchFlags, SlotFlags } from '@vue/shared'
|
||||||
|
|
||||||
@ -517,4 +520,60 @@ describe('renderer: optimized mode', () => {
|
|||||||
expect(spyA).toHaveBeenCalledTimes(1)
|
expect(spyA).toHaveBeenCalledTimes(1)
|
||||||
expect(spyB).toHaveBeenCalledTimes(1)
|
expect(spyB).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #2893
|
||||||
|
test('manually rendering the optimized slots should allow subsequent updates to exit the optimized mode correctly', async () => {
|
||||||
|
const state = ref(0)
|
||||||
|
|
||||||
|
const CompA = {
|
||||||
|
setup(props: any, { slots }: SetupContext) {
|
||||||
|
return () => {
|
||||||
|
return (
|
||||||
|
openBlock(),
|
||||||
|
createBlock('div', null, [renderSlot(slots, 'default')])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = {
|
||||||
|
setup(props: any, { slots }: SetupContext) {
|
||||||
|
// use the manually written render function to rendering the optimized slots,
|
||||||
|
// which should make subsequent updates exit the optimized mode correctly
|
||||||
|
return () => {
|
||||||
|
return slots.default!()[state.value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
setup() {
|
||||||
|
return () => {
|
||||||
|
return (
|
||||||
|
openBlock(),
|
||||||
|
createBlock(Wrapper, null, {
|
||||||
|
default: withCtx(() => [
|
||||||
|
createVNode(CompA, null, {
|
||||||
|
default: withCtx(() => [createTextVNode('Hello')]),
|
||||||
|
_: 1 /* STABLE */
|
||||||
|
}),
|
||||||
|
createVNode(CompA, null, {
|
||||||
|
default: withCtx(() => [createTextVNode('World')]),
|
||||||
|
_: 1 /* STABLE */
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
_: 1 /* STABLE */
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.mount(root)
|
||||||
|
expect(inner(root)).toBe('<div>Hello</div>')
|
||||||
|
|
||||||
|
state.value = 1
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toBe('<div>World</div>')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -130,7 +130,8 @@ export const initSlots = (
|
|||||||
|
|
||||||
export const updateSlots = (
|
export const updateSlots = (
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
children: VNodeNormalizedChildren
|
children: VNodeNormalizedChildren,
|
||||||
|
optimized: boolean
|
||||||
) => {
|
) => {
|
||||||
const { vnode, slots } = instance
|
const { vnode, slots } = instance
|
||||||
let needDeletionCheck = true
|
let needDeletionCheck = true
|
||||||
@ -143,7 +144,7 @@ export const updateSlots = (
|
|||||||
// Parent was HMR updated so slot content may have changed.
|
// Parent was HMR updated so slot content may have changed.
|
||||||
// force update slots and mark instance for hmr as well
|
// force update slots and mark instance for hmr as well
|
||||||
extend(slots, children as Slots)
|
extend(slots, children as Slots)
|
||||||
} else if (type === SlotFlags.STABLE) {
|
} else if (optimized && type === SlotFlags.STABLE) {
|
||||||
// compiled AND stable.
|
// compiled AND stable.
|
||||||
// no need to update, and skip stale slots removal.
|
// no need to update, and skip stale slots removal.
|
||||||
needDeletionCheck = false
|
needDeletionCheck = false
|
||||||
@ -151,6 +152,13 @@ export const updateSlots = (
|
|||||||
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
|
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
|
||||||
// normalization.
|
// normalization.
|
||||||
extend(slots, children as Slots)
|
extend(slots, children as Slots)
|
||||||
|
// #2893
|
||||||
|
// when rendering the optimized slots by manually written render function,
|
||||||
|
// we need to delete the `slots._` flag if necessary to make subsequent updates reliable,
|
||||||
|
// i.e. let the `renderSlot` create the bailed Fragment
|
||||||
|
if (!optimized && type === SlotFlags.STABLE) {
|
||||||
|
delete slots._
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
needDeletionCheck = !(children as RawSlots).$stable
|
needDeletionCheck = !(children as RawSlots).$stable
|
||||||
|
@ -1576,7 +1576,7 @@ function baseCreateRenderer(
|
|||||||
instance.vnode = nextVNode
|
instance.vnode = nextVNode
|
||||||
instance.next = null
|
instance.next = null
|
||||||
updateProps(instance, nextVNode.props, prevProps, optimized)
|
updateProps(instance, nextVNode.props, prevProps, optimized)
|
||||||
updateSlots(instance, nextVNode.children)
|
updateSlots(instance, nextVNode.children, optimized)
|
||||||
|
|
||||||
pauseTracking()
|
pauseTracking()
|
||||||
// props update may have triggered pre-flush watchers.
|
// props update may have triggered pre-flush watchers.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user