From 00036bb52c4e641b2be7fa55c39ced9448163b0f Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 30 Aug 2022 15:55:09 +0800 Subject: [PATCH] fix(slots): ensure different branches of dynamic slots have different keys fix #6202 --- .../__snapshots__/scopeId.spec.ts.snap | 3 ++- .../__snapshots__/vSlot.spec.ts.snap | 15 ++++++++---- .../__tests__/transforms/vSlot.spec.ts | 15 ++++++++---- .../compiler-core/src/transforms/vSlot.ts | 24 ++++++++++++++----- .../__tests__/ssrComponent.spec.ts | 3 ++- .../__tests__/helpers/createSlots.spec.ts | 9 +++++++ .../runtime-core/src/helpers/createSlots.ts | 11 ++++++++- .../runtime-core/src/helpers/renderSlot.ts | 9 ++++++- 8 files changed, 69 insertions(+), 20 deletions(-) diff --git a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap index a250f325d0a..826bc4a027f 100644 --- a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap @@ -43,7 +43,8 @@ export function render(_ctx, _cache) { name: \\"foo\\", fn: _withCtx(() => [ _createElementVNode(\\"div\\") - ]) + ]), + key: \\"0\\" } : undefined, _renderList(_ctx.list, (i) => { diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 967f2b3f45e..55796020a47 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -56,7 +56,8 @@ return function render(_ctx, _cache) { (_ctx.ok) ? { name: \\"one\\", - fn: _withCtx((props) => [_toDisplayString(props)]) + fn: _withCtx((props) => [_toDisplayString(props)]), + key: \\"0\\" } : undefined ]), 1024 /* DYNAMIC_SLOTS */)) @@ -76,16 +77,19 @@ return function render(_ctx, _cache) { ok ? { name: \\"one\\", - fn: _withCtx(() => [\\"foo\\"]) + fn: _withCtx(() => [\\"foo\\"]), + key: \\"0\\" } : orNot ? { name: \\"two\\", - fn: _withCtx((props) => [\\"bar\\"]) + fn: _withCtx((props) => [\\"bar\\"]), + key: \\"1\\" } : { name: \\"one\\", - fn: _withCtx(() => [\\"baz\\"]) + fn: _withCtx(() => [\\"baz\\"]), + key: \\"2\\" } ]), 1024 /* DYNAMIC_SLOTS */)) } @@ -105,7 +109,8 @@ return function render(_ctx, _cache) { ok ? { name: \\"one\\", - fn: _withCtx(() => [\\"hello\\"]) + fn: _withCtx(() => [\\"hello\\"]), + key: \\"0\\" } : undefined ]), 1024 /* DYNAMIC_SLOTS */)) diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index 687c4d8b358..93dafe9a25b 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -568,7 +568,8 @@ describe('compiler: transform component slots', () => { fn: { type: NodeTypes.JS_FUNCTION_EXPRESSION, returns: [{ type: NodeTypes.TEXT, content: `hello` }] - } + }, + key: `0` }), alternate: { content: `undefined`, @@ -616,7 +617,8 @@ describe('compiler: transform component slots', () => { content: { content: `props` } } ] - } + }, + key: `0` }), alternate: { content: `undefined`, @@ -660,7 +662,8 @@ describe('compiler: transform component slots', () => { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: undefined, returns: [{ type: NodeTypes.TEXT, content: `foo` }] - } + }, + key: `0` }), alternate: { type: NodeTypes.JS_CONDITIONAL_EXPRESSION, @@ -671,7 +674,8 @@ describe('compiler: transform component slots', () => { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: { content: `props` }, returns: [{ type: NodeTypes.TEXT, content: `bar` }] - } + }, + key: `1` }), alternate: createObjectMatcher({ name: `one`, @@ -679,7 +683,8 @@ describe('compiler: transform component slots', () => { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: undefined, returns: [{ type: NodeTypes.TEXT, content: `baz` }] - } + }, + key: `2` }) } } diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 8fc86740eb6..c4416dd45f7 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -160,6 +160,7 @@ export function buildSlots( let hasNamedDefaultSlot = false const implicitDefaultChildren: TemplateChildNode[] = [] const seenSlotNames = new Set() + let conditionalBranchIndex = 0 for (let i = 0; i < children.length; i++) { const slotElement = children[i] @@ -210,7 +211,7 @@ export function buildSlots( dynamicSlots.push( createConditionalExpression( vIf.exp!, - buildDynamicSlot(slotName, slotFunction), + buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++), defaultFallback ) ) @@ -243,10 +244,14 @@ export function buildSlots( conditional.alternate = vElse.exp ? createConditionalExpression( vElse.exp, - buildDynamicSlot(slotName, slotFunction), + buildDynamicSlot( + slotName, + slotFunction, + conditionalBranchIndex++ + ), defaultFallback ) - : buildDynamicSlot(slotName, slotFunction) + : buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++) } else { context.onError( createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc) @@ -369,12 +374,19 @@ export function buildSlots( function buildDynamicSlot( name: ExpressionNode, - fn: FunctionExpression + fn: FunctionExpression, + index?: number ): ObjectExpression { - return createObjectExpression([ + const props = [ createObjectProperty(`name`, name), createObjectProperty(`fn`, fn) - ]) + ] + if (index != null) { + props.push( + createObjectProperty(`key`, createSimpleExpression(String(index), true)) + ) + } + return createObjectExpression(props) } function hasForwardedSlots(children: TemplateChildNode[]): boolean { diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts index 1a796186739..5d5191ffb44 100644 --- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts @@ -166,7 +166,8 @@ describe('ssr: components', () => { _createTextVNode(\\"foo\\") ] } - }) + }), + key: \\"0\\" } : undefined ]), _parent)) diff --git a/packages/runtime-core/__tests__/helpers/createSlots.spec.ts b/packages/runtime-core/__tests__/helpers/createSlots.spec.ts index 8c6d76997b3..891789d048a 100644 --- a/packages/runtime-core/__tests__/helpers/createSlots.spec.ts +++ b/packages/runtime-core/__tests__/helpers/createSlots.spec.ts @@ -17,6 +17,15 @@ describe('createSlot', () => { expect(actual).toEqual({ descriptor: slot }) }) + it('should attach key', () => { + const dynamicSlot = [{ name: 'descriptor', fn: slot, key: '1' }] + + const actual = createSlots(record, dynamicSlot) + const ret = actual.descriptor() + // @ts-ignore + expect(ret.key).toBe('1') + }) + it('should add all slots to the record', () => { const dynamicSlot = [ { name: 'descriptor', fn: slot }, diff --git a/packages/runtime-core/src/helpers/createSlots.ts b/packages/runtime-core/src/helpers/createSlots.ts index 4ec50496146..89ea7ac77c8 100644 --- a/packages/runtime-core/src/helpers/createSlots.ts +++ b/packages/runtime-core/src/helpers/createSlots.ts @@ -4,6 +4,7 @@ import { isArray } from '@vue/shared' interface CompiledSlotDescriptor { name: string fn: Slot + key?: string } /** @@ -27,7 +28,15 @@ export function createSlots( } } else if (slot) { // conditional single slot generated by