瀏覽代碼

feat(hydration): hydrate VaporTransition (#14001)

edison 6 月之前
父節點
當前提交
f1fcada83f

+ 7 - 7
packages/runtime-core/src/hydration.ts

@@ -805,16 +805,16 @@ export function createHydrationFunctions(
     }
   }
 
-  const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
-    return (
-      node.nodeType === DOMNodeTypes.ELEMENT &&
-      (node as Element).tagName === 'TEMPLATE'
-    )
-  }
-
   return [hydrate, hydrateNode]
 }
 
+export const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
+  return (
+    node.nodeType === DOMNodeTypes.ELEMENT &&
+    (node as Element).tagName === 'TEMPLATE'
+  )
+}
+
 /**
  * Dev only
  */

+ 4 - 0
packages/runtime-core/src/index.ts

@@ -644,3 +644,7 @@ export {
  * @internal
  */
 export { createCanSetSetupRefChecker } from './rendererTemplateRef'
+/**
+ * @internal
+ */
+export { isTemplateNode } from './hydration'

+ 1 - 1
packages/runtime-vapor/__tests__/hydration.spec.ts

@@ -2897,7 +2897,7 @@ describe('Vapor Mode hydration', () => {
     })
   })
 
-  describe.todo('transition', async () => {
+  describe('transition', async () => {
     test('transition appear', async () => {
       const { container } = await testHydration(
         `<template>

+ 44 - 2
packages/runtime-vapor/src/components/Transition.ts

@@ -1,4 +1,5 @@
 import {
+  type BaseTransitionProps,
   type GenericComponentInstance,
   type TransitionElement,
   type TransitionHooks,
@@ -9,7 +10,9 @@ import {
   baseResolveTransitionHooks,
   checkTransitionMode,
   currentInstance,
+  isTemplateNode,
   leaveCbKey,
+  queuePostFlushCb,
   resolveTransitionProps,
   useTransitionState,
   warn,
@@ -24,6 +27,11 @@ import {
 import { extend, isArray } from '@vue/shared'
 import { renderEffect } from '../renderEffect'
 import { isFragment } from '../fragment'
+import {
+  currentHydrationNode,
+  isHydrating,
+  setCurrentHydrationNode,
+} from '../dom/hydration'
 
 const decorate = (t: typeof VaporTransition) => {
   t.displayName = 'VaporTransition'
@@ -34,6 +42,33 @@ const decorate = (t: typeof VaporTransition) => {
 
 export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
   (props, { slots, attrs }) => {
+    // wrapped <transition appear>
+    let resetDisplay: Function | undefined
+    if (
+      isHydrating &&
+      currentHydrationNode &&
+      isTemplateNode(currentHydrationNode)
+    ) {
+      // replace <template> node with inner child
+      const {
+        content: { firstChild },
+        parentNode,
+      } = currentHydrationNode
+      if (firstChild) {
+        if (
+          firstChild instanceof HTMLElement ||
+          firstChild instanceof SVGElement
+        ) {
+          const originalDisplay = firstChild.style.display
+          firstChild.style.display = 'none'
+          resetDisplay = () => (firstChild.style.display = originalDisplay)
+        }
+
+        parentNode!.replaceChild(firstChild, currentHydrationNode)
+        setCurrentHydrationNode(firstChild)
+      }
+    }
+
     const children = (slots.default && slots.default()) as any as Block
     if (!children) return
 
@@ -41,7 +76,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
     const { mode } = props
     checkTransitionMode(mode)
 
-    let resolvedProps
+    let resolvedProps: BaseTransitionProps<Element>
     let isMounted = false
     renderEffect(() => {
       resolvedProps = resolveTransitionProps(props)
@@ -81,7 +116,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
       })
     }
 
-    applyTransitionHooks(
+    const hooks = applyTransitionHooks(
       children,
       {
         state: useTransitionState(),
@@ -91,6 +126,13 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
       fallthroughAttrs,
     )
 
+    if (resetDisplay && resolvedProps!.appear) {
+      const child = findTransitionBlock(children)!
+      hooks.beforeEnter(child)
+      resetDisplay()
+      queuePostFlushCb(() => hooks.enter(child))
+    }
+
     return children
   },
 )

+ 32 - 30
packages/runtime-vapor/src/directives/vShow.ts

@@ -61,44 +61,46 @@ function setDisplay(target: Block, value: unknown): void {
       el[vShowOriginalDisplay] =
         el.style.display === 'none' ? '' : el.style.display
     }
-    if (
-      (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
-      isHydrating
-    ) {
-      if (!value && el.style.display !== 'none') {
-        warnPropMismatch(
-          el,
-          'style',
-          MismatchTypes.STYLE,
-          `display: ${el.style.display}`,
-          'display: none',
-        )
-        logMismatchError()
 
-        el.style.display = 'none'
-        el[vShowOriginalDisplay] = ''
+    if ($transition) {
+      if (value) {
+        $transition.beforeEnter(target)
+        el.style.display = el[vShowOriginalDisplay]!
+        $transition.enter(target)
+      } else {
+        // during initial render, the element is not yet inserted into the
+        // DOM, and it is hidden, no need to trigger transition
+        if (target.isConnected) {
+          $transition.leave(target, () => {
+            el.style.display = 'none'
+          })
+        } else {
+          el.style.display = 'none'
+        }
       }
     } else {
-      if ($transition) {
-        if (value) {
-          $transition.beforeEnter(target)
-          el.style.display = el[vShowOriginalDisplay]!
-          $transition.enter(target)
-        } else {
-          // during initial render, the element is not yet inserted into the
-          // DOM, and it is hidden, no need to trigger transition
-          if (target.isConnected) {
-            $transition.leave(target, () => {
-              el.style.display = 'none'
-            })
-          } else {
-            el.style.display = 'none'
-          }
+      if (
+        (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
+        isHydrating
+      ) {
+        if (!value && el.style.display !== 'none') {
+          warnPropMismatch(
+            el,
+            'style',
+            MismatchTypes.STYLE,
+            `display: ${el.style.display}`,
+            'display: none',
+          )
+          logMismatchError()
+
+          el.style.display = 'none'
+          el[vShowOriginalDisplay] = ''
         }
       } else {
         el.style.display = value ? el[vShowOriginalDisplay]! : 'none'
       }
     }
+
     el[vShowHidden] = !value
   } else if (__DEV__) {
     warn(