mirror of https://github.com/vuejs/core.git
fix(runtime-core): more edge case fix for manually rendered compiled slot
close #11336
This commit is contained in:
parent
314ce82e47
commit
685e3f381c
|
@ -1040,4 +1040,69 @@ describe('renderer: optimized mode', () => {
|
|||
expect(app.config.errorHandler).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
// #11336
|
||||
test('should bail manually rendered compiler slots for both mount and update (2)', async () => {
|
||||
// only reproducible in prod
|
||||
__DEV__ = false
|
||||
const n = ref(0)
|
||||
function Outer(_: any, { slots }: any) {
|
||||
n.value // track
|
||||
return slots.default()
|
||||
}
|
||||
const Mid = {
|
||||
render(ctx: any) {
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock('div', null, [renderSlot(ctx.$slots, 'default')])
|
||||
)
|
||||
},
|
||||
}
|
||||
const show = ref(false)
|
||||
const App = {
|
||||
render() {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(Outer, null, {
|
||||
default: withCtx(() => [
|
||||
createVNode(Mid, null, {
|
||||
default: withCtx(() => [
|
||||
createElementVNode('div', null, [
|
||||
show.value
|
||||
? (openBlock(),
|
||||
createElementBlock('div', { key: 0 }, '1'))
|
||||
: createCommentVNode('v-if', true),
|
||||
createElementVNode('div', null, '2'),
|
||||
createElementVNode('div', null, '3'),
|
||||
]),
|
||||
createElementVNode('div', null, '4'),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
}),
|
||||
]),
|
||||
_: 1 /* STABLE */,
|
||||
})
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const app = createApp(App)
|
||||
app.config.errorHandler = vi.fn()
|
||||
|
||||
try {
|
||||
app.mount(root)
|
||||
|
||||
// force Outer update, which will assign new slots to Mid
|
||||
// we want to make sure the compiled slot flag doesn't accidentally
|
||||
// get assigned again
|
||||
n.value++
|
||||
await nextTick()
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
expect(app.config.errorHandler).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
ShapeFlags,
|
||||
SlotFlags,
|
||||
def,
|
||||
extend,
|
||||
isArray,
|
||||
isFunction,
|
||||
} from '@vue/shared'
|
||||
|
@ -161,6 +160,22 @@ const normalizeVNodeSlots = (
|
|||
instance.slots.default = () => normalized
|
||||
}
|
||||
|
||||
const assignSlots = (
|
||||
slots: InternalSlots,
|
||||
children: Slots,
|
||||
optimized: boolean,
|
||||
) => {
|
||||
for (const key in children) {
|
||||
// #2893
|
||||
// when rendering the optimized slots by manually written render function,
|
||||
// do not copy the `slots._` compiler flag so that `renderSlot` creates
|
||||
// slot Fragment with BAIL patchFlag to force full updates
|
||||
if (optimized || key !== '_') {
|
||||
slots[key] = children[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const initSlots = (
|
||||
instance: ComponentInternalInstance,
|
||||
children: VNodeNormalizedChildren,
|
||||
|
@ -170,16 +185,10 @@ export const initSlots = (
|
|||
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
||||
const type = (children as RawSlots)._
|
||||
if (type) {
|
||||
extend(slots, children as InternalSlots)
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
// make compiler marker non-enumerable
|
||||
if (optimized) {
|
||||
def(slots, '_', type, true)
|
||||
} else {
|
||||
// #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
|
||||
delete slots._
|
||||
}
|
||||
} else {
|
||||
normalizeObjectSlots(children as RawSlots, slots, instance)
|
||||
|
@ -204,7 +213,7 @@ export const updateSlots = (
|
|||
if (__DEV__ && isHmrUpdating) {
|
||||
// Parent was HMR updated so slot content may have changed.
|
||||
// force update slots and mark instance for hmr as well
|
||||
extend(slots, children as Slots)
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
trigger(instance, TriggerOpTypes.SET, '$slots')
|
||||
} else if (optimized && type === SlotFlags.STABLE) {
|
||||
// compiled AND stable.
|
||||
|
@ -213,7 +222,7 @@ export const updateSlots = (
|
|||
} else {
|
||||
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
|
||||
// normalization.
|
||||
extend(slots, children as Slots)
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
}
|
||||
} else {
|
||||
needDeletionCheck = !(children as RawSlots).$stable
|
||||
|
|
Loading…
Reference in New Issue