]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(slots): ensure different branches of dynamic slots have different keys
authorEvan You <yyx990803@gmail.com>
Tue, 30 Aug 2022 07:55:09 +0000 (15:55 +0800)
committerEvan You <yyx990803@gmail.com>
Tue, 30 Aug 2022 07:55:09 +0000 (15:55 +0800)
fix #6202

packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
packages/compiler-core/src/transforms/vSlot.ts
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
packages/runtime-core/__tests__/helpers/createSlots.spec.ts
packages/runtime-core/src/helpers/createSlots.ts
packages/runtime-core/src/helpers/renderSlot.ts

index a250f325d0a23069feb635c161eb0571fa2f329f..826bc4a027fb0f45cea0ed764bfb03d8b8ecda20 100644 (file)
@@ -43,7 +43,8 @@ export function render(_ctx, _cache) {
           name: \\"foo\\",
           fn: _withCtx(() => [
             _createElementVNode(\\"div\\")
-          ])
+          ]),
+          key: \\"0\\"
         }
       : undefined,
     _renderList(_ctx.list, (i) => {
index 967f2b3f45e7caefef706d6057b9aff0ce6603f3..55796020a47fd588354f56c17ecd19f32e30109d 100644 (file)
@@ -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 */))
index 687c4d8b358f0a1f98234a413b267f117557232e..93dafe9a25b7dfacab7e016828070aba46364b98 100644 (file)
@@ -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`
                 })
               }
             }
index 8fc86740eb6314d1d296abbe03e35bfcdae70980..c4416dd45f77d188d67660635c5ae8427dba876d 100644 (file)
@@ -160,6 +160,7 @@ export function buildSlots(
   let hasNamedDefaultSlot = false
   const implicitDefaultChildren: TemplateChildNode[] = []
   const seenSlotNames = new Set<string>()
+  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 {
index 1a7961867391a0facc658c5fa70087ff82e5ebe4..5d5191ffb4494108acb8c55d92c0704b6b87dfc4 100644 (file)
@@ -166,7 +166,8 @@ describe('ssr: components', () => {
                         _createTextVNode(\\"foo\\")
                       ]
                     }
-                  })
+                  }),
+                  key: \\"0\\"
                 }
               : undefined
           ]), _parent))
index 8c6d76997b36b400c8d5fcd4091ee4990c1e3433..891789d048aa9907256de2333e2102f5fdc8103d 100644 (file)
@@ -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 },
index 4ec504961466a65c34e520b257940747aaddabf1..89ea7ac77c825ac73529a8017833d0a38f02b238 100644 (file)
@@ -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 <template v-if="..." #foo>
-      slots[slot.name] = slot.fn
+      slots[slot.name] = slot.key
+        ? (...args: any[]) => {
+            const res = slot.fn(...args)
+            // attach branch key so each conditional branch is considered a
+            // different fragment
+            ;(res as any).key = slot.key
+            return res
+          }
+        : slot.fn
     }
   }
   return slots
index 9310ee0b8f4e551e7a5ee6c27228719a140cdf81..a92309daee392c5598e085818ce49b57e3b1efd9 100644 (file)
@@ -66,7 +66,14 @@ export function renderSlot(
   const validSlotContent = slot && ensureValidVNode(slot(props))
   const rendered = createBlock(
     Fragment,
-    { key: props.key || `_${name}` },
+    {
+      key:
+        props.key ||
+        // slot content array of a dynamic conditional slot may have a branch
+        // key attached in the `createSlots` helper, respect that
+        (validSlotContent && (validSlotContent as any).key) ||
+        `_${name}`
+    },
     validSlotContent || (fallback ? fallback() : []),
     validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
       ? PatchFlags.STABLE_FRAGMENT