feat(transition): support component child
This commit is contained in:
parent
93561b080e
commit
79f23a2f77
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user