]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(transition): support component child
authorEvan You <yyx990803@gmail.com>
Thu, 21 Nov 2019 02:56:17 +0000 (21:56 -0500)
committerEvan You <yyx990803@gmail.com>
Thu, 21 Nov 2019 03:46:32 +0000 (22:46 -0500)
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/componentRenderUtils.ts
packages/runtime-core/src/components/Transition.ts
packages/runtime-core/src/errorHandling.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts

index ead0784db603c5c4cc39a03c31cf6f26310fec89..5d921f3bfafe96814b29da0f327f881bf7f58f55 100644 (file)
@@ -8,7 +8,6 @@ import {
   isFunction,
   isArray,
   isObject,
-  isReservedProp,
   hasOwn,
   toRawType,
   PatchFlags,
@@ -122,8 +121,8 @@ export function resolveProps(
 
   if (rawProps != null) {
     for (const key in rawProps) {
-      // key, ref are reserved
-      if (isReservedProp(key)) continue
+      // key, ref are reserved and never passed down
+      if (key === 'key' || key === 'ref') continue
       // prop option names are camelized during normalization, so to support
       // kebab -> camel conversion here we need to camelize the key.
       const camelKey = camelize(key)
index e5964d0db179dc7991a6101087b6d5a919ed5960..d42982d580b9ca14978c5b4c92acf6c8cfd7b7e1 100644 (file)
@@ -85,6 +85,12 @@ export function renderComponentRoot(
         )
       }
     }
+
+    // inherit transition data
+    if (vnode.transition != null) {
+      // TODO warn if component has transition data but root is a fragment
+      result.transition = vnode.transition
+    }
   } catch (err) {
     handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
     result = createVNode(Comment)
index 8b96af7ca661f758e1a0663e0fa67dbcd2684fa7..c02265fda81e4a04a112e2c0ae488f196d92bcb9 100644 (file)
@@ -1,17 +1,12 @@
 import { createComponent } from '../apiCreateComponent'
-import { getCurrentInstance } from '../component'
-import {
-  cloneVNode,
-  Comment,
-  isSameVNodeType,
-  VNodeProps,
-  VNode,
-  mergeProps
-} from '../vnode'
+import { getCurrentInstance, ComponentInternalInstance } from '../component'
+import { cloneVNode, Comment, isSameVNodeType, VNode } from '../vnode'
 import { warn } from '../warning'
 import { isKeepAlive } from './KeepAlive'
 import { toRaw } from '@vue/reactivity'
 import { onMounted } from '../apiLifecycle'
+import { callWithAsyncErrorHandling, ErrorCodes } from '../errorHandling'
+import { ShapeFlags } from '../shapeFlags'
 
 // Using camel case here makes it easier to use in render functions & JSX.
 // In templates these will be written as @before-enter="xxx"
@@ -66,52 +61,51 @@ export const Transition = createComponent({
       }
 
       // at this point children has a guaranteed length of 1.
-      const rawChild = children[0]
+      const child = children[0]
       if (isLeaving) {
-        return placeholder(rawChild)
+        return placeholder(child)
       }
 
-      rawChild.transition = rawProps
+      let delayedLeave: (() => void) | undefined
+      const performDelayedLeave = () => delayedLeave && delayedLeave()
+      const transitionData = (child.transition = resolveTransitionData(
+        instance,
+        rawProps,
+        isMounted,
+        performDelayedLeave
+      ))
+
       // clone old subTree because we need to modify it
       const oldChild = instance.subTree
         ? (instance.subTree = cloneVNode(instance.subTree))
         : null
 
       // handle mode
-      let performDelayedLeave: (() => void) | undefined
       if (
         oldChild &&
-        !isSameVNodeType(rawChild, oldChild) &&
+        !isSameVNodeType(child, oldChild) &&
         oldChild.type !== Comment
       ) {
         // update old tree's hooks in case of dynamic transition
-        oldChild.transition = rawProps
+        // need to do this recursively in case of HOCs
+        updateHOCTransitionData(oldChild, transitionData)
         // switching between different views
         if (mode === 'out-in') {
           isLeaving = true
           // return placeholder node and queue update when leave finishes
-          oldChild.props = mergeProps(oldChild.props!, {
-            onVnodeRemoved() {
-              isLeaving = false
-              instance.update()
-            }
-          })
-          return placeholder(rawChild)
+          transitionData.afterLeave = () => {
+            isLeaving = false
+            instance.update()
+          }
+          return placeholder(child)
         } else if (mode === 'in-out') {
-          let delayedLeave: () => void
-          performDelayedLeave = () => delayedLeave()
-          oldChild.props = mergeProps(oldChild.props!, {
-            onVnodeDelayLeave(performLeave) {
-              delayedLeave = performLeave
-            }
-          })
+          transitionData.delayLeave = performLeave => {
+            delayedLeave = performLeave
+          }
         }
       }
 
-      return cloneVNode(
-        rawChild,
-        resolveTransitionInjections(rawProps, isMounted, performDelayedLeave)
-      )
+      return child
     }
   }
 })
@@ -133,7 +127,16 @@ if (__DEV__) {
   }
 }
 
-function resolveTransitionInjections(
+export interface TransitionData {
+  beforeEnter(el: object): void
+  enter(el: object): void
+  leave(el: object, remove: () => void): void
+  afterLeave?(): void
+  delayLeave?(performLeave: () => void): void
+}
+
+function resolveTransitionData(
+  instance: ComponentInternalInstance,
   {
     appear,
     onBeforeEnter,
@@ -146,24 +149,35 @@ function resolveTransitionInjections(
     onLeaveCancelled
   }: TransitionProps,
   isMounted: boolean,
-  performDelayedLeave?: () => void
-): VNodeProps {
-  // TODO handle appear
+  performDelayedLeave: () => void
+): TransitionData {
   // TODO handle cancel hooks
   return {
-    onVnodeBeforeMount(vnode) {
+    beforeEnter(el) {
       if (!isMounted && !appear) {
         return
       }
-      onBeforeEnter && onBeforeEnter(vnode.el)
+      onBeforeEnter &&
+        callWithAsyncErrorHandling(
+          onBeforeEnter,
+          instance,
+          ErrorCodes.TRANSITION_HOOK,
+          [el]
+        )
     },
-    onVnodeMounted({ el }) {
+    enter(el) {
       if (!isMounted && !appear) {
         return
       }
       const done = () => {
-        onAfterEnter && onAfterEnter(el)
-        performDelayedLeave && performDelayedLeave()
+        onAfterEnter &&
+          callWithAsyncErrorHandling(
+            onAfterEnter,
+            instance,
+            ErrorCodes.TRANSITION_HOOK,
+            [el]
+          )
+        performDelayedLeave()
       }
       if (onEnter) {
         onEnter(el, done)
@@ -171,16 +185,30 @@ function resolveTransitionInjections(
         done()
       }
     },
-    onVnodeBeforeRemove({ el }, remove) {
-      onBeforeLeave && onBeforeLeave(el)
+    leave(el, remove) {
+      onBeforeLeave &&
+        callWithAsyncErrorHandling(
+          onBeforeLeave,
+          instance,
+          ErrorCodes.TRANSITION_HOOK,
+          [el]
+        )
+      const afterLeave = () =>
+        onAfterLeave &&
+        callWithAsyncErrorHandling(
+          onAfterLeave,
+          instance,
+          ErrorCodes.TRANSITION_HOOK,
+          [el]
+        )
       if (onLeave) {
         onLeave(el, () => {
           remove()
-          onAfterLeave && onAfterLeave(el)
+          afterLeave()
         })
       } else {
         remove()
-        onAfterLeave && onAfterLeave(el)
+        afterLeave()
       }
     }
   }
@@ -197,3 +225,11 @@ function placeholder(vnode: VNode): VNode | undefined {
     return vnode
   }
 }
+
+function updateHOCTransitionData(vnode: VNode, data: TransitionData) {
+  if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
+    updateHOCTransitionData(vnode.component!.subTree, data)
+  } else {
+    vnode.transition = data
+  }
+}
index 88d5af350e5c7bffea1908433d4642b6559c06ca..c71524ac88e5d40ca0acfddfe66724d9c800159d 100644 (file)
@@ -14,6 +14,7 @@ export const enum ErrorCodes {
   NATIVE_EVENT_HANDLER,
   COMPONENT_EVENT_HANDLER,
   DIRECTIVE_HOOK,
+  TRANSITION_HOOK,
   APP_ERROR_HANDLER,
   APP_WARN_HANDLER,
   FUNCTION_REF,
@@ -42,6 +43,7 @@ export const ErrorTypeStrings: Record<number | string, string> = {
   [ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
   [ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
   [ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
+  [ErrorCodes.TRANSITION_HOOK]: 'transition hook',
   [ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
   [ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
   [ErrorCodes.FUNCTION_REF]: 'ref function',
index 5ca2ca06313011350da633570a2d135ca89968ef..da3e777cec603a30c2865719f87e74342b6e8ce5 100644 (file)
@@ -27,8 +27,7 @@ import {
   EMPTY_ARR,
   isReservedProp,
   isFunction,
-  PatchFlags,
-  isArray
+  PatchFlags
 } from '@vue/shared'
 import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
 import {
@@ -52,11 +51,7 @@ import {
   queueEffectWithSuspense,
   SuspenseImpl
 } from './components/Suspense'
-import {
-  ErrorCodes,
-  callWithErrorHandling,
-  callWithAsyncErrorHandling
-} from './errorHandling'
+import { ErrorCodes, callWithErrorHandling } from './errorHandling'
 import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
 
 export interface RendererOptions<HostNode = any, HostElement = any> {
@@ -362,7 +357,7 @@ export function createRenderer<
     const tag = vnode.type as string
     isSVG = isSVG || tag === 'svg'
     const el = (vnode.el = hostCreateElement(tag, isSVG))
-    const { props, shapeFlag } = vnode
+    const { props, shapeFlag, transition } = vnode
     if (props != null) {
       for (const key in props) {
         if (isReservedProp(key)) continue
@@ -372,6 +367,9 @@ export function createRenderer<
         invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
       }
     }
+    if (transition != null) {
+      transition.beforeEnter(el)
+    }
     if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
       hostSetElementText(el, vnode.children as string)
     } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
@@ -386,9 +384,12 @@ export function createRenderer<
       )
     }
     hostInsert(el, container, anchor)
-    if (props != null && props.onVnodeMounted != null) {
+    const vnodeMountedHook = props && props.onVnodeMounted
+    if (vnodeMountedHook != null || transition != null) {
       queuePostRenderEffect(() => {
-        invokeDirectiveHook(props.onVnodeMounted!, parentComponent, vnode)
+        vnodeMountedHook &&
+          invokeDirectiveHook(vnodeMountedHook, parentComponent, vnode)
+        transition && transition.enter(el)
       }, parentSuspense)
     }
   }
@@ -1412,13 +1413,15 @@ export function createRenderer<
     doRemove?: boolean
   ) {
     const {
+      el,
       props,
       ref,
       type,
       children,
       dynamicChildren,
       shapeFlag,
-      anchor
+      anchor,
+      transition
     } = vnode
 
     // unset ref
@@ -1462,23 +1465,16 @@ export function createRenderer<
     }
 
     if (doRemove) {
-      const beforeRemoveHooks = props && props.onVnodeBeforeRemove
       const remove = () => {
         hostRemove(vnode.el!)
         if (anchor != null) hostRemove(anchor)
-        const removedHook = props && props.onVnodeRemoved
-        removedHook && removedHook()
-      }
-      if (vnode.shapeFlag & ShapeFlags.ELEMENT && beforeRemoveHooks != null) {
-        const delayLeave = props && props.onVnodeDelayLeave
-        const performLeave = () => {
-          invokeBeforeRemoveHooks(
-            beforeRemoveHooks,
-            parentComponent,
-            vnode,
-            remove
-          )
+        if (transition != null && transition.afterLeave) {
+          transition.afterLeave()
         }
+      }
+      if (vnode.shapeFlag & ShapeFlags.ELEMENT && transition != null) {
+        const { leave, delayLeave } = transition
+        const performLeave = () => leave(el!, remove)
         if (delayLeave) {
           delayLeave(performLeave)
         } else {
@@ -1496,37 +1492,6 @@ export function createRenderer<
     }
   }
 
-  function invokeBeforeRemoveHooks(
-    hooks: ((...args: any[]) => any) | ((...args: any[]) => any)[],
-    instance: ComponentInternalInstance | null,
-    vnode: HostVNode,
-    done: () => void
-  ) {
-    if (!isArray(hooks)) {
-      hooks = [hooks]
-    }
-    let delayedRemoveCount = hooks.length
-    const doneRemove = () => {
-      delayedRemoveCount--
-      if (allHooksCalled && !delayedRemoveCount) {
-        done()
-      }
-    }
-    let allHooksCalled = false
-    for (let i = 0; i < hooks.length; i++) {
-      callWithAsyncErrorHandling(
-        hooks[i],
-        instance,
-        ErrorCodes.DIRECTIVE_HOOK,
-        [vnode, doneRemove]
-      )
-    }
-    allHooksCalled = true
-    if (!delayedRemoveCount) {
-      done()
-    }
-  }
-
   function unmountComponent(
     instance: ComponentInternalInstance,
     parentSuspense: HostSuspenseBoundary | null,
index 3134057feebede682a9f9ff2308c8938b1fb0326..c7b32bdfe4f46413a8095e78db664b4ea56b19de 100644 (file)
@@ -19,7 +19,7 @@ import { AppContext } from './apiApp'
 import { SuspenseBoundary } from './components/Suspense'
 import { DirectiveBinding } from './directives'
 import { SuspenseImpl } from './components/Suspense'
-import { TransitionProps } from './components/Transition'
+import { TransitionData } from './components/Transition'
 
 export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
   __isFragment: true
@@ -57,11 +57,6 @@ export interface VNodeProps {
   onVnodeUpdated?: (vnode: VNode, oldVNode: VNode) => void
   onVnodeBeforeUnmount?: (vnode: VNode) => void
   onVnodeUnmounted?: (vnode: VNode) => void
-
-  // transition hooks, internal.
-  onVnodeDelayLeave?: (performLeave: () => void) => void
-  onVnodeBeforeRemove?: (vnode: VNode, remove: () => void) => void
-  onVnodeRemoved?: () => void
 }
 
 type VNodeChildAtom<HostNode, HostElement> =
@@ -98,7 +93,7 @@ export interface VNode<HostNode = any, HostElement = any> {
   component: ComponentInternalInstance | null
   suspense: SuspenseBoundary<HostNode, HostElement> | null
   dirs: DirectiveBinding[] | null
-  transition: TransitionProps | null
+  transition: TransitionData | null
 
   // DOM
   el: HostNode | null