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()
|
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,
|
ShapeFlags,
|
||||||
SlotFlags,
|
SlotFlags,
|
||||||
def,
|
def,
|
||||||
extend,
|
|
||||||
isArray,
|
isArray,
|
||||||
isFunction,
|
isFunction,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
@ -161,6 +160,22 @@ const normalizeVNodeSlots = (
|
||||||
instance.slots.default = () => normalized
|
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 = (
|
export const initSlots = (
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
children: VNodeNormalizedChildren,
|
children: VNodeNormalizedChildren,
|
||||||
|
@ -170,16 +185,10 @@ export const initSlots = (
|
||||||
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
||||||
const type = (children as RawSlots)._
|
const type = (children as RawSlots)._
|
||||||
if (type) {
|
if (type) {
|
||||||
extend(slots, children as InternalSlots)
|
assignSlots(slots, children as Slots, optimized)
|
||||||
// make compiler marker non-enumerable
|
// make compiler marker non-enumerable
|
||||||
if (optimized) {
|
if (optimized) {
|
||||||
def(slots, '_', type, true)
|
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 {
|
} else {
|
||||||
normalizeObjectSlots(children as RawSlots, slots, instance)
|
normalizeObjectSlots(children as RawSlots, slots, instance)
|
||||||
|
@ -204,7 +213,7 @@ export const updateSlots = (
|
||||||
if (__DEV__ && isHmrUpdating) {
|
if (__DEV__ && isHmrUpdating) {
|
||||||
// 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)
|
assignSlots(slots, children as Slots, optimized)
|
||||||
trigger(instance, TriggerOpTypes.SET, '$slots')
|
trigger(instance, TriggerOpTypes.SET, '$slots')
|
||||||
} else if (optimized && type === SlotFlags.STABLE) {
|
} else if (optimized && type === SlotFlags.STABLE) {
|
||||||
// compiled AND stable.
|
// compiled AND stable.
|
||||||
|
@ -213,7 +222,7 @@ export const updateSlots = (
|
||||||
} else {
|
} else {
|
||||||
// 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)
|
assignSlots(slots, children as Slots, optimized)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
needDeletionCheck = !(children as RawSlots).$stable
|
needDeletionCheck = !(children as RawSlots).$stable
|
||||||
|
|
Loading…
Reference in New Issue