fix(runtime-core): bail manually rendered compiler slot fragments in all cases

Previously this bail was only applied on updates but not on initial mount,
and leads to different patch code paths between mount and update in edge
cases.

close #10870
This commit is contained in:
Evan You 2024-07-12 00:34:05 +08:00
parent 23cd61423c
commit 3d34f406ac
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
5 changed files with 32 additions and 13 deletions

View File

@ -7,7 +7,7 @@ import {
ref,
render,
} from '@vue/runtime-test'
import { normalizeVNode } from '../src/vnode'
import { createBlock, normalizeVNode } from '../src/vnode'
import { createSlots } from '../src/helpers/createSlots'
describe('component: slots', () => {
@ -25,8 +25,21 @@ describe('component: slots', () => {
}
test('initSlots: instance.slots should be set correctly', () => {
let instance: any
const Comp = {
render() {
instance = getCurrentInstance()
return h('div')
},
}
const slots = { foo: () => {}, _: 1 }
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
expect(instance.slots).toMatchObject(slots)
})
test('initSlots: instance.slots should remove compiler marker if parent is using manual render function', () => {
const { slots } = renderWithSlots({ _: 1 })
expect(slots).toMatchObject({ _: 1 })
expect(slots).toMatchObject({})
})
test('initSlots: should normalize object slots (when value is null, string, array)', () => {

View File

@ -434,7 +434,7 @@ describe('renderer: optimized mode', () => {
const App = {
setup() {
return () => {
return createVNode(Comp, null, {
return createBlock(Comp, null, {
default: withCtx(() => [
createVNode('p', null, foo.value, PatchFlags.TEXT),
]),
@ -560,6 +560,7 @@ describe('renderer: optimized mode', () => {
const state = ref(0)
const CompA = {
name: 'A',
setup(props: any, { slots }: SetupContext) {
return () => {
return (
@ -571,6 +572,7 @@ describe('renderer: optimized mode', () => {
}
const Wrapper = {
name: '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
@ -581,6 +583,7 @@ describe('renderer: optimized mode', () => {
}
const app = createApp({
name: 'App',
setup() {
return () => {
return (

View File

@ -736,13 +736,14 @@ export let isInSSRComponentSetup = false
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false,
optimized = false,
) {
isSSR && setInSSRSetupState(isSSR)
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
initSlots(instance, children, optimized)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)

View File

@ -164,6 +164,7 @@ const normalizeVNodeSlots = (
export const initSlots = (
instance: ComponentInternalInstance,
children: VNodeNormalizedChildren,
optimized: boolean,
) => {
const slots = (instance.slots = createInternalObject())
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
@ -171,7 +172,15 @@ export const initSlots = (
if (type) {
extend(slots, children as InternalSlots)
// make compiler marker non-enumerable
def(slots, '_', type, true)
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)
}
@ -205,13 +214,6 @@ export const updateSlots = (
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
// normalization.
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 {
needDeletionCheck = !(children as RawSlots).$stable

View File

@ -1229,7 +1229,7 @@ function baseCreateRenderer(
if (__DEV__) {
startMeasure(instance, `init`)
}
setupComponent(instance)
setupComponent(instance, false, optimized)
if (__DEV__) {
endMeasure(instance, `init`)
}