feat(transition): handle cancel hooks

This commit is contained in:
Evan You 2019-11-22 17:10:17 -05:00
parent 231b940103
commit 5c691aebfd
2 changed files with 67 additions and 54 deletions

View File

@ -1,6 +1,5 @@
import { import {
getCurrentInstance, getCurrentInstance,
ComponentInternalInstance,
SetupContext, SetupContext,
ComponentOptions ComponentOptions
} from '../component' } from '../component'
@ -30,17 +29,38 @@ export interface TransitionProps {
onLeaveCancelled?: (el: any) => void 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 = { const TransitionImpl = {
name: `BaseTransition`, name: `BaseTransition`,
setup(props: TransitionProps, { slots }: SetupContext) { setup(props: TransitionProps, { slots }: SetupContext) {
const instance = getCurrentInstance()! const instance = getCurrentInstance()!
let isLeaving = false let isLeaving = false
let isMounted = false let isMounted = false
const pendingCallbacks: PendingCallbacks = {}
onMounted(() => { onMounted(() => {
isMounted = true isMounted = true
}) })
const callTransitionHook: TransitionHookCaller = (hook, args) => {
hook &&
callWithAsyncErrorHandling(
hook,
instance,
ErrorCodes.TRANSITION_HOOK,
args
)
}
return () => { return () => {
const children = slots.default && slots.default() const children = slots.default && slots.default()
if (!children || !children.length) { if (!children || !children.length) {
@ -72,10 +92,11 @@ const TransitionImpl = {
let delayedLeave: (() => void) | undefined let delayedLeave: (() => void) | undefined
const performDelayedLeave = () => delayedLeave && delayedLeave() const performDelayedLeave = () => delayedLeave && delayedLeave()
const transitionData = (child.transition = resolveTransitionData( const transitionHooks = (child.transition = resolveTransitionHooks(
instance,
rawProps, rawProps,
callTransitionHook,
isMounted, isMounted,
pendingCallbacks,
performDelayedLeave performDelayedLeave
)) ))
@ -92,18 +113,18 @@ const TransitionImpl = {
) { ) {
// update old tree's hooks in case of dynamic transition // update old tree's hooks in case of dynamic transition
// need to do this recursively in case of HOCs // need to do this recursively in case of HOCs
updateHOCTransitionData(oldChild, transitionData) updateHOCTransitionData(oldChild, transitionHooks)
// switching between different views // switching between different views
if (mode === 'out-in') { if (mode === 'out-in') {
isLeaving = true isLeaving = true
// return placeholder node and queue update when leave finishes // return placeholder node and queue update when leave finishes
transitionData.afterLeave = () => { transitionHooks.afterLeave = () => {
isLeaving = false isLeaving = false
instance.update() instance.update()
} }
return placeholder(child) return placeholder(child)
} else if (mode === 'in-out') { } else if (mode === 'in-out') {
transitionData.delayLeave = performLeave => { transitionHooks.delayLeave = performLeave => {
delayedLeave = performLeave delayedLeave = performLeave
} }
} }
@ -139,7 +160,7 @@ export const Transition = (TransitionImpl as any) as {
} }
} }
export interface TransitionData { export interface TransitionHooks {
beforeEnter(el: object): void beforeEnter(el: object): void
enter(el: object): void enter(el: object): void
leave(el: object, remove: () => void): void leave(el: object, remove: () => void): void
@ -147,8 +168,7 @@ export interface TransitionData {
delayLeave?(performLeave: () => void): void delayLeave?(performLeave: () => void): void
} }
function resolveTransitionData( function resolveTransitionHooks(
instance: ComponentInternalInstance,
{ {
appear, appear,
onBeforeEnter, onBeforeEnter,
@ -160,66 +180,59 @@ function resolveTransitionData(
onAfterLeave, onAfterLeave,
onLeaveCancelled onLeaveCancelled
}: TransitionProps, }: TransitionProps,
callHook: TransitionHookCaller,
isMounted: boolean, isMounted: boolean,
pendingCallbacks: PendingCallbacks,
performDelayedLeave: () => void performDelayedLeave: () => void
): TransitionData { ): TransitionHooks {
// TODO handle cancel hooks
return { return {
beforeEnter(el) { beforeEnter(el) {
if (!isMounted && !appear) { if (!isMounted && !appear) {
return return
} }
onBeforeEnter && if (pendingCallbacks.leave) {
callWithAsyncErrorHandling( pendingCallbacks.leave(true /* cancelled */)
onBeforeEnter, }
instance, callHook(onBeforeEnter, [el])
ErrorCodes.TRANSITION_HOOK,
[el]
)
}, },
enter(el) { enter(el) {
if (!isMounted && !appear) { if (!isMounted && !appear) {
return return
} }
const done = () => { const afterEnter = (pendingCallbacks.enter = (cancelled?) => {
onAfterEnter && if (cancelled) {
callWithAsyncErrorHandling( callHook(onEnterCancelled, [el])
onAfterEnter, } else {
instance, callHook(onAfterEnter, [el])
ErrorCodes.TRANSITION_HOOK,
[el]
)
performDelayedLeave() performDelayedLeave()
} }
pendingCallbacks.enter = undefined
})
if (onEnter) { if (onEnter) {
onEnter(el, done) onEnter(el, afterEnter)
} else { } else {
done() afterEnter()
} }
}, },
leave(el, remove) { leave(el, remove) {
onBeforeLeave && if (pendingCallbacks.enter) {
callWithAsyncErrorHandling( pendingCallbacks.enter(true /* cancelled */)
onBeforeLeave, }
instance, callHook(onBeforeLeave, [el])
ErrorCodes.TRANSITION_HOOK, const afterLeave = (pendingCallbacks.leave = (cancelled?) => {
[el]
)
const afterLeave = () =>
onAfterLeave &&
callWithAsyncErrorHandling(
onAfterLeave,
instance,
ErrorCodes.TRANSITION_HOOK,
[el]
)
if (onLeave) {
onLeave(el, () => {
remove() remove()
afterLeave() if (cancelled) {
}) callHook(onLeaveCancelled, [el])
} else {
callHook(onAfterLeave, [el])
}
pendingCallbacks.leave = undefined
})
if (onLeave) {
onLeave(el, afterLeave)
} else { } else {
remove()
afterLeave() 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) { if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
updateHOCTransitionData(vnode.component!.subTree, data) updateHOCTransitionData(vnode.component!.subTree, data)
} else { } else {

View File

@ -19,7 +19,7 @@ import { AppContext } from './apiApp'
import { SuspenseBoundary } from './components/Suspense' import { SuspenseBoundary } from './components/Suspense'
import { DirectiveBinding } from './directives' import { DirectiveBinding } from './directives'
import { SuspenseImpl } from './components/Suspense' 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 { export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true __isFragment: true
@ -93,7 +93,7 @@ export interface VNode<HostNode = any, HostElement = any> {
component: ComponentInternalInstance | null component: ComponentInternalInstance | null
suspense: SuspenseBoundary<HostNode, HostElement> | null suspense: SuspenseBoundary<HostNode, HostElement> | null
dirs: DirectiveBinding[] | null dirs: DirectiveBinding[] | null
transition: TransitionData | null transition: TransitionHooks | null
// DOM // DOM
el: HostNode | null el: HostNode | null