From 3d34f406ac7497dafd2f4e62ab23579b78a0e08a Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 12 Jul 2024 00:34:05 +0800 Subject: [PATCH] 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 --- .../__tests__/componentSlots.spec.ts | 17 +++++++++++++++-- .../__tests__/rendererOptimizedMode.spec.ts | 5 ++++- packages/runtime-core/src/component.ts | 3 ++- packages/runtime-core/src/componentSlots.ts | 18 ++++++++++-------- packages/runtime-core/src/renderer.ts | 2 +- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/runtime-core/__tests__/componentSlots.spec.ts b/packages/runtime-core/__tests__/componentSlots.spec.ts index 09b379321..6042ccbd7 100644 --- a/packages/runtime-core/__tests__/componentSlots.spec.ts +++ b/packages/runtime-core/__tests__/componentSlots.spec.ts @@ -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)', () => { diff --git a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts index 556ab7520..4176f0fd4 100644 --- a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts +++ b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts @@ -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 ( diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index b5faa856e..df3a63769 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -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) diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index 2bc3466c4..0145d557b 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -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 diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index d5c5b6d8d..1f36502c7 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1229,7 +1229,7 @@ function baseCreateRenderer( if (__DEV__) { startMeasure(instance, `init`) } - setupComponent(instance) + setupComponent(instance, false, optimized) if (__DEV__) { endMeasure(instance, `init`) }