]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: handle keyed element transition
authordaiwei <daiwei521@126.com>
Tue, 4 Mar 2025 09:55:13 +0000 (17:55 +0800)
committerdaiwei <daiwei521@126.com>
Tue, 4 Mar 2025 14:02:27 +0000 (22:02 +0800)
packages/compiler-vapor/src/generators/block.ts
packages/compiler-vapor/src/generators/component.ts
packages/compiler-vapor/src/ir/index.ts
packages/compiler-vapor/src/transforms/vIf.ts
packages/compiler-vapor/src/transforms/vSlot.ts
packages/runtime-vapor/src/apiCreateFragment.ts [new file with mode: 0644]
packages/runtime-vapor/src/index.ts

index 4e568a91e7f49dda7bf6efe9c00ceef87b961e9b..39354e252a904249247272c3a07252bce4881fab 100644 (file)
@@ -13,6 +13,7 @@ import type { CodegenContext } from '../generate'
 import { genEffects, genOperations } from './operation'
 import { genChildren } from './template'
 import { toValidAssetId } from '@vue/compiler-dom'
+import { genExpression } from './expression'
 
 export function genBlock(
   oper: BlockIRNode,
@@ -40,7 +41,7 @@ export function genBlockContent(
   customReturns?: (returns: CodeFragment[]) => CodeFragment[],
 ): CodeFragment[] {
   const [frag, push] = buildCodeFragment()
-  const { dynamic, effect, operation, returns } = block
+  const { dynamic, effect, operation, returns, key } = block
   const resetBlock = context.enterBlock(block)
 
   if (root) {
@@ -57,7 +58,10 @@ export function genBlockContent(
 
   if (dynamic.needsKey) {
     for (const child of dynamic.children) {
-      push(NEWLINE, `n${child.id}.key = ${JSON.stringify(child.id)}`)
+      const keyValue = key
+        ? genExpression(key, context)
+        : JSON.stringify(child.id)
+      push(NEWLINE, `n${child.id}.key = `, ...keyValue)
     }
   }
 
index 8b0ab73e78ff97977d6014a60f88c843360b2b09..c0ac494641db34369d16575395b78bb42dea56be 100644 (file)
@@ -99,7 +99,7 @@ export function genCreateComponent(
         return `_${builtInTag}`
       }
       return genExpression(
-        extend(createSimpleExpression(operation.tag, false), { ast: null }),
+        extend(createSimpleExpression(tag, false), { ast: null }),
         context,
       )
     }
@@ -402,7 +402,7 @@ function genSlotBlockWithProps(oper: SlotBlockIRNode, context: CodegenContext) {
   let propsName: string | undefined
   let exitScope: (() => void) | undefined
   let depth: number | undefined
-  const { props } = oper
+  const { props, key } = oper
   const idsOfProps = new Set<string>()
 
   if (props) {
@@ -430,11 +430,28 @@ function genSlotBlockWithProps(oper: SlotBlockIRNode, context: CodegenContext) {
         ? `${propsName}[${JSON.stringify(id)}]`
         : null),
   )
-  const blockFn = context.withId(
+  let blockFn = context.withId(
     () => genBlock(oper, context, [propsName]),
     idMap,
   )
   exitScope && exitScope()
 
+  if (key) {
+    blockFn = [
+      `() => {`,
+      INDENT_START,
+      NEWLINE,
+      `return `,
+      ...genCall(
+        context.helper('createKeyedFragment'),
+        [`() => `, ...genExpression(key, context)],
+        blockFn,
+      ),
+      INDENT_END,
+      NEWLINE,
+      `}`,
+    ]
+  }
+
   return blockFn
 }
index b048e40d79b47a457a8a7793ac7c3069928ec343..71e896e13f19d527e7cf129f42c5b79a057043b9 100644 (file)
@@ -40,6 +40,7 @@ export enum IRNodeTypes {
 
 export interface BaseIRNode {
   type: IRNodeTypes
+  key?: SimpleExpressionNode | undefined
 }
 
 export type CoreHelper = keyof typeof import('packages/runtime-dom/src')
index bf9b178fd34e5008853a1d9175db194d1a6b7d0c..5306cf70573d3f7f9775cc2c2041bc75a8022d8b 100644 (file)
@@ -130,11 +130,16 @@ export function createIfBranch(
   return [branch, exitBlock]
 }
 
-function isInTransition(context: TransformContext<ElementNode>): boolean {
+export function isInTransition(
+  context: TransformContext<ElementNode>,
+): boolean {
   const parentNode = context.parent && context.parent.node
-  return !!(
-    parentNode &&
-    parentNode.type === NodeTypes.ELEMENT &&
-    (parentNode.tag === 'transition' || parentNode.tag === 'Transition')
+  return !!(parentNode && isTransitionNode(parentNode as ElementNode))
+}
+
+export function isTransitionNode(node: ElementNode): boolean {
+  return (
+    node.type === NodeTypes.ELEMENT &&
+    (node.tag === 'transition' || node.tag === 'Transition')
   )
 }
index d1bf1c6b05f54fd3344e2c12024361971a04785c..c1b82e2bc576a03843203e0e6f5ccdef94f93858 100644 (file)
@@ -23,7 +23,8 @@ import {
   type SlotBlockIRNode,
   type VaporDirectiveNode,
 } from '../ir'
-import { findDir, resolveExpression } from '../utils'
+import { findDir, findProp, resolveExpression } from '../utils'
+import { isTransitionNode } from './vIf'
 
 export const transformVSlot: NodeTransform = (node, context) => {
   if (node.type !== NodeTypes.ELEMENT) return
@@ -72,7 +73,18 @@ function transformComponentSlot(
       !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
   )
 
-  const [block, onExit] = createSlotBlock(node, dir, context)
+  let slotKey
+  if (isTransitionNode(node)) {
+    const keyProp = findProp(
+      nonSlotTemplateChildren[0] as ElementNode,
+      'key',
+    ) as VaporDirectiveNode
+    if (keyProp) {
+      slotKey = keyProp.exp
+    }
+  }
+
+  const [block, onExit] = createSlotBlock(node, dir, context, slotKey)
 
   const { slots } = context
 
@@ -233,9 +245,12 @@ function createSlotBlock(
   slotNode: ElementNode,
   dir: VaporDirectiveNode | undefined,
   context: TransformContext<ElementNode>,
+  key: SimpleExpressionNode | undefined = undefined,
 ): [SlotBlockIRNode, () => void] {
   const block: SlotBlockIRNode = newBlock(slotNode)
   block.props = dir && dir.exp
+  block.key = key
+  if (key) block.dynamic.needsKey = true
   const exitBlock = context.enterBlock(block)
   return [block, exitBlock]
 }
diff --git a/packages/runtime-vapor/src/apiCreateFragment.ts b/packages/runtime-vapor/src/apiCreateFragment.ts
new file mode 100644 (file)
index 0000000..50179b8
--- /dev/null
@@ -0,0 +1,10 @@
+import { type Block, type BlockFn, DynamicFragment } from './block'
+import { renderEffect } from './renderEffect'
+
+export function createKeyedFragment(key: () => any, render: BlockFn): Block {
+  const frag = __DEV__ ? new DynamicFragment('keyed') : new DynamicFragment()
+  renderEffect(() => {
+    frag.update(render, key())
+  })
+  return frag
+}
index 40a847ba8f5833013a327bf27fd314e111f1b140..c356727f8625cb0fdeebeedaa03aca69b9e62627 100644 (file)
@@ -24,6 +24,7 @@ export {
 } from './dom/prop'
 export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
 export { createIf } from './apiCreateIf'
+export { createKeyedFragment } from './apiCreateFragment'
 export {
   createFor,
   createForSlots,