From 5c691aebfd65311e38134e539057cd1dd8051445 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 22 Nov 2019 17:10:17 -0500 Subject: [PATCH] feat(transition): handle cancel hooks --- .../runtime-core/src/components/Transition.ts | 117 ++++++++++-------- packages/runtime-core/src/vnode.ts | 4 +- 2 files changed, 67 insertions(+), 54 deletions(-) diff --git a/packages/runtime-core/src/components/Transition.ts b/packages/runtime-core/src/components/Transition.ts index 501ffc3c..34d9e5f1 100644 --- a/packages/runtime-core/src/components/Transition.ts +++ b/packages/runtime-core/src/components/Transition.ts @@ -1,6 +1,5 @@ import { getCurrentInstance, - ComponentInternalInstance, SetupContext, ComponentOptions } from '../component' @@ -30,17 +29,38 @@ export interface TransitionProps { onLeaveCancelled?: (el: any) => void } +type TransitionHookCaller = ( + hook: ((el: any) => void) | undefined, + args?: any[] +) => void + +interface PendingCallbacks { + enter?: (cancelled?: boolean) => void + leave?: (cancelled?: boolean) => void +} + const TransitionImpl = { name: `BaseTransition`, setup(props: TransitionProps, { slots }: SetupContext) { const instance = getCurrentInstance()! let isLeaving = false let isMounted = false + const pendingCallbacks: PendingCallbacks = {} onMounted(() => { isMounted = true }) + const callTransitionHook: TransitionHookCaller = (hook, args) => { + hook && + callWithAsyncErrorHandling( + hook, + instance, + ErrorCodes.TRANSITION_HOOK, + args + ) + } + return () => { const children = slots.default && slots.default() if (!children || !children.length) { @@ -72,10 +92,11 @@ const TransitionImpl = { let delayedLeave: (() => void) | undefined const performDelayedLeave = () => delayedLeave && delayedLeave() - const transitionData = (child.transition = resolveTransitionData( - instance, + const transitionHooks = (child.transition = resolveTransitionHooks( rawProps, + callTransitionHook, isMounted, + pendingCallbacks, performDelayedLeave )) @@ -92,18 +113,18 @@ const TransitionImpl = { ) { // update old tree's hooks in case of dynamic transition // need to do this recursively in case of HOCs - updateHOCTransitionData(oldChild, transitionData) + updateHOCTransitionData(oldChild, transitionHooks) // switching between different views if (mode === 'out-in') { isLeaving = true // return placeholder node and queue update when leave finishes - transitionData.afterLeave = () => { + transitionHooks.afterLeave = () => { isLeaving = false instance.update() } return placeholder(child) } else if (mode === 'in-out') { - transitionData.delayLeave = performLeave => { + transitionHooks.delayLeave = performLeave => { delayedLeave = performLeave } } @@ -139,7 +160,7 @@ export const Transition = (TransitionImpl as any) as { } } -export interface TransitionData { +export interface TransitionHooks { beforeEnter(el: object): void enter(el: object): void leave(el: object, remove: () => void): void @@ -147,8 +168,7 @@ export interface TransitionData { delayLeave?(performLeave: () => void): void } -function resolveTransitionData( - instance: ComponentInternalInstance, +function resolveTransitionHooks( { appear, onBeforeEnter, @@ -160,66 +180,59 @@ function resolveTransitionData( onAfterLeave, onLeaveCancelled }: TransitionProps, + callHook: TransitionHookCaller, isMounted: boolean, + pendingCallbacks: PendingCallbacks, performDelayedLeave: () => void -): TransitionData { - // TODO handle cancel hooks +): TransitionHooks { return { beforeEnter(el) { if (!isMounted && !appear) { return } - onBeforeEnter && - callWithAsyncErrorHandling( - onBeforeEnter, - instance, - ErrorCodes.TRANSITION_HOOK, - [el] - ) + if (pendingCallbacks.leave) { + pendingCallbacks.leave(true /* cancelled */) + } + callHook(onBeforeEnter, [el]) }, + enter(el) { if (!isMounted && !appear) { return } - const done = () => { - onAfterEnter && - callWithAsyncErrorHandling( - onAfterEnter, - instance, - ErrorCodes.TRANSITION_HOOK, - [el] - ) - performDelayedLeave() - } + const afterEnter = (pendingCallbacks.enter = (cancelled?) => { + if (cancelled) { + callHook(onEnterCancelled, [el]) + } else { + callHook(onAfterEnter, [el]) + performDelayedLeave() + } + pendingCallbacks.enter = undefined + }) if (onEnter) { - onEnter(el, done) + onEnter(el, afterEnter) } else { - done() + afterEnter() } }, + 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() - afterLeave() - }) - } else { + if (pendingCallbacks.enter) { + pendingCallbacks.enter(true /* cancelled */) + } + callHook(onBeforeLeave, [el]) + const afterLeave = (pendingCallbacks.leave = (cancelled?) => { remove() + if (cancelled) { + callHook(onLeaveCancelled, [el]) + } else { + callHook(onAfterLeave, [el]) + } + pendingCallbacks.leave = undefined + }) + if (onLeave) { + onLeave(el, afterLeave) + } else { afterLeave() } } @@ -238,7 +251,7 @@ function placeholder(vnode: VNode): VNode | undefined { } } -function updateHOCTransitionData(vnode: VNode, data: TransitionData) { +function updateHOCTransitionData(vnode: VNode, data: TransitionHooks) { if (vnode.shapeFlag & ShapeFlags.COMPONENT) { updateHOCTransitionData(vnode.component!.subTree, data) } else { diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index c7b32bdf..a56cc976 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -19,7 +19,7 @@ import { AppContext } from './apiApp' import { SuspenseBoundary } from './components/Suspense' import { DirectiveBinding } from './directives' import { SuspenseImpl } from './components/Suspense' -import { TransitionData } from './components/Transition' +import { TransitionHooks } from './components/Transition' export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { __isFragment: true @@ -93,7 +93,7 @@ export interface VNode { component: ComponentInternalInstance | null suspense: SuspenseBoundary | null dirs: DirectiveBinding[] | null - transition: TransitionData | null + transition: TransitionHooks | null // DOM el: HostNode | null