feat(transition): support component child
This commit is contained in:
parent
93561b080e
commit
79f23a2f77
@ -8,7 +8,6 @@ import {
|
|||||||
isFunction,
|
isFunction,
|
||||||
isArray,
|
isArray,
|
||||||
isObject,
|
isObject,
|
||||||
isReservedProp,
|
|
||||||
hasOwn,
|
hasOwn,
|
||||||
toRawType,
|
toRawType,
|
||||||
PatchFlags,
|
PatchFlags,
|
||||||
@ -122,8 +121,8 @@ export function resolveProps(
|
|||||||
|
|
||||||
if (rawProps != null) {
|
if (rawProps != null) {
|
||||||
for (const key in rawProps) {
|
for (const key in rawProps) {
|
||||||
// key, ref are reserved
|
// key, ref are reserved and never passed down
|
||||||
if (isReservedProp(key)) continue
|
if (key === 'key' || key === 'ref') continue
|
||||||
// prop option names are camelized during normalization, so to support
|
// prop option names are camelized during normalization, so to support
|
||||||
// kebab -> camel conversion here we need to camelize the key.
|
// kebab -> camel conversion here we need to camelize the key.
|
||||||
const camelKey = camelize(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) {
|
} catch (err) {
|
||||||
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
|
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
|
||||||
result = createVNode(Comment)
|
result = createVNode(Comment)
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
import { createComponent } from '../apiCreateComponent'
|
import { createComponent } from '../apiCreateComponent'
|
||||||
import { getCurrentInstance } from '../component'
|
import { getCurrentInstance, ComponentInternalInstance } from '../component'
|
||||||
import {
|
import { cloneVNode, Comment, isSameVNodeType, VNode } from '../vnode'
|
||||||
cloneVNode,
|
|
||||||
Comment,
|
|
||||||
isSameVNodeType,
|
|
||||||
VNodeProps,
|
|
||||||
VNode,
|
|
||||||
mergeProps
|
|
||||||
} from '../vnode'
|
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
import { isKeepAlive } from './KeepAlive'
|
import { isKeepAlive } from './KeepAlive'
|
||||||
import { toRaw } from '@vue/reactivity'
|
import { toRaw } from '@vue/reactivity'
|
||||||
import { onMounted } from '../apiLifecycle'
|
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.
|
// Using camel case here makes it easier to use in render functions & JSX.
|
||||||
// In templates these will be written as @before-enter="xxx"
|
// 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.
|
// at this point children has a guaranteed length of 1.
|
||||||
const rawChild = children[0]
|
const child = children[0]
|
||||||
if (isLeaving) {
|
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
|
// clone old subTree because we need to modify it
|
||||||
const oldChild = instance.subTree
|
const oldChild = instance.subTree
|
||||||
? (instance.subTree = cloneVNode(instance.subTree))
|
? (instance.subTree = cloneVNode(instance.subTree))
|
||||||
: null
|
: null
|
||||||
|
|
||||||
// handle mode
|
// handle mode
|
||||||
let performDelayedLeave: (() => void) | undefined
|
|
||||||
if (
|
if (
|
||||||
oldChild &&
|
oldChild &&
|
||||||
!isSameVNodeType(rawChild, oldChild) &&
|
!isSameVNodeType(child, oldChild) &&
|
||||||
oldChild.type !== Comment
|
oldChild.type !== Comment
|
||||||
) {
|
) {
|
||||||
// update old tree's hooks in case of dynamic transition
|
// 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
|
// 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
|
||||||
oldChild.props = mergeProps(oldChild.props!, {
|
transitionData.afterLeave = () => {
|
||||||
onVnodeRemoved() {
|
isLeaving = false
|
||||||
isLeaving = false
|
instance.update()
|
||||||
instance.update()
|
}
|
||||||
}
|
return placeholder(child)
|
||||||
})
|
|
||||||
return placeholder(rawChild)
|
|
||||||
} else if (mode === 'in-out') {
|
} else if (mode === 'in-out') {
|
||||||
let delayedLeave: () => void
|
transitionData.delayLeave = performLeave => {
|
||||||
performDelayedLeave = () => delayedLeave()
|
delayedLeave = performLeave
|
||||||
oldChild.props = mergeProps(oldChild.props!, {
|
}
|
||||||
onVnodeDelayLeave(performLeave) {
|
|
||||||
delayedLeave = performLeave
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloneVNode(
|
return child
|
||||||
rawChild,
|
|
||||||
resolveTransitionInjections(rawProps, isMounted, performDelayedLeave)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -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,
|
appear,
|
||||||
onBeforeEnter,
|
onBeforeEnter,
|
||||||
@ -146,24 +149,35 @@ function resolveTransitionInjections(
|
|||||||
onLeaveCancelled
|
onLeaveCancelled
|
||||||
}: TransitionProps,
|
}: TransitionProps,
|
||||||
isMounted: boolean,
|
isMounted: boolean,
|
||||||
performDelayedLeave?: () => void
|
performDelayedLeave: () => void
|
||||||
): VNodeProps {
|
): TransitionData {
|
||||||
// TODO handle appear
|
|
||||||
// TODO handle cancel hooks
|
// TODO handle cancel hooks
|
||||||
return {
|
return {
|
||||||
onVnodeBeforeMount(vnode) {
|
beforeEnter(el) {
|
||||||
if (!isMounted && !appear) {
|
if (!isMounted && !appear) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onBeforeEnter && onBeforeEnter(vnode.el)
|
onBeforeEnter &&
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
onBeforeEnter,
|
||||||
|
instance,
|
||||||
|
ErrorCodes.TRANSITION_HOOK,
|
||||||
|
[el]
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onVnodeMounted({ el }) {
|
enter(el) {
|
||||||
if (!isMounted && !appear) {
|
if (!isMounted && !appear) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const done = () => {
|
const done = () => {
|
||||||
onAfterEnter && onAfterEnter(el)
|
onAfterEnter &&
|
||||||
performDelayedLeave && performDelayedLeave()
|
callWithAsyncErrorHandling(
|
||||||
|
onAfterEnter,
|
||||||
|
instance,
|
||||||
|
ErrorCodes.TRANSITION_HOOK,
|
||||||
|
[el]
|
||||||
|
)
|
||||||
|
performDelayedLeave()
|
||||||
}
|
}
|
||||||
if (onEnter) {
|
if (onEnter) {
|
||||||
onEnter(el, done)
|
onEnter(el, done)
|
||||||
@ -171,16 +185,30 @@ function resolveTransitionInjections(
|
|||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onVnodeBeforeRemove({ el }, remove) {
|
leave(el, remove) {
|
||||||
onBeforeLeave && onBeforeLeave(el)
|
onBeforeLeave &&
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
onBeforeLeave,
|
||||||
|
instance,
|
||||||
|
ErrorCodes.TRANSITION_HOOK,
|
||||||
|
[el]
|
||||||
|
)
|
||||||
|
const afterLeave = () =>
|
||||||
|
onAfterLeave &&
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
onAfterLeave,
|
||||||
|
instance,
|
||||||
|
ErrorCodes.TRANSITION_HOOK,
|
||||||
|
[el]
|
||||||
|
)
|
||||||
if (onLeave) {
|
if (onLeave) {
|
||||||
onLeave(el, () => {
|
onLeave(el, () => {
|
||||||
remove()
|
remove()
|
||||||
onAfterLeave && onAfterLeave(el)
|
afterLeave()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
remove()
|
remove()
|
||||||
onAfterLeave && onAfterLeave(el)
|
afterLeave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,3 +225,11 @@ function placeholder(vnode: VNode): VNode | undefined {
|
|||||||
return vnode
|
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,
|
NATIVE_EVENT_HANDLER,
|
||||||
COMPONENT_EVENT_HANDLER,
|
COMPONENT_EVENT_HANDLER,
|
||||||
DIRECTIVE_HOOK,
|
DIRECTIVE_HOOK,
|
||||||
|
TRANSITION_HOOK,
|
||||||
APP_ERROR_HANDLER,
|
APP_ERROR_HANDLER,
|
||||||
APP_WARN_HANDLER,
|
APP_WARN_HANDLER,
|
||||||
FUNCTION_REF,
|
FUNCTION_REF,
|
||||||
@ -42,6 +43,7 @@ export const ErrorTypeStrings: Record<number | string, string> = {
|
|||||||
[ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
[ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
||||||
[ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
[ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
||||||
[ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
|
[ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
|
||||||
|
[ErrorCodes.TRANSITION_HOOK]: 'transition hook',
|
||||||
[ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
|
[ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
|
||||||
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
||||||
[ErrorCodes.FUNCTION_REF]: 'ref function',
|
[ErrorCodes.FUNCTION_REF]: 'ref function',
|
||||||
|
@ -27,8 +27,7 @@ import {
|
|||||||
EMPTY_ARR,
|
EMPTY_ARR,
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
isFunction,
|
isFunction,
|
||||||
PatchFlags,
|
PatchFlags
|
||||||
isArray
|
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
||||||
import {
|
import {
|
||||||
@ -52,11 +51,7 @@ import {
|
|||||||
queueEffectWithSuspense,
|
queueEffectWithSuspense,
|
||||||
SuspenseImpl
|
SuspenseImpl
|
||||||
} from './components/Suspense'
|
} from './components/Suspense'
|
||||||
import {
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
ErrorCodes,
|
|
||||||
callWithErrorHandling,
|
|
||||||
callWithAsyncErrorHandling
|
|
||||||
} from './errorHandling'
|
|
||||||
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
|
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
|
||||||
|
|
||||||
export interface RendererOptions<HostNode = any, HostElement = any> {
|
export interface RendererOptions<HostNode = any, HostElement = any> {
|
||||||
@ -362,7 +357,7 @@ export function createRenderer<
|
|||||||
const tag = vnode.type as string
|
const tag = vnode.type as string
|
||||||
isSVG = isSVG || tag === 'svg'
|
isSVG = isSVG || tag === 'svg'
|
||||||
const el = (vnode.el = hostCreateElement(tag, isSVG))
|
const el = (vnode.el = hostCreateElement(tag, isSVG))
|
||||||
const { props, shapeFlag } = vnode
|
const { props, shapeFlag, transition } = vnode
|
||||||
if (props != null) {
|
if (props != null) {
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
if (isReservedProp(key)) continue
|
if (isReservedProp(key)) continue
|
||||||
@ -372,6 +367,9 @@ export function createRenderer<
|
|||||||
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
|
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (transition != null) {
|
||||||
|
transition.beforeEnter(el)
|
||||||
|
}
|
||||||
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
||||||
hostSetElementText(el, vnode.children as string)
|
hostSetElementText(el, vnode.children as string)
|
||||||
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||||
@ -386,9 +384,12 @@ export function createRenderer<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
hostInsert(el, container, anchor)
|
hostInsert(el, container, anchor)
|
||||||
if (props != null && props.onVnodeMounted != null) {
|
const vnodeMountedHook = props && props.onVnodeMounted
|
||||||
|
if (vnodeMountedHook != null || transition != null) {
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
invokeDirectiveHook(props.onVnodeMounted!, parentComponent, vnode)
|
vnodeMountedHook &&
|
||||||
|
invokeDirectiveHook(vnodeMountedHook, parentComponent, vnode)
|
||||||
|
transition && transition.enter(el)
|
||||||
}, parentSuspense)
|
}, parentSuspense)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1412,13 +1413,15 @@ export function createRenderer<
|
|||||||
doRemove?: boolean
|
doRemove?: boolean
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
|
el,
|
||||||
props,
|
props,
|
||||||
ref,
|
ref,
|
||||||
type,
|
type,
|
||||||
children,
|
children,
|
||||||
dynamicChildren,
|
dynamicChildren,
|
||||||
shapeFlag,
|
shapeFlag,
|
||||||
anchor
|
anchor,
|
||||||
|
transition
|
||||||
} = vnode
|
} = vnode
|
||||||
|
|
||||||
// unset ref
|
// unset ref
|
||||||
@ -1462,23 +1465,16 @@ export function createRenderer<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (doRemove) {
|
if (doRemove) {
|
||||||
const beforeRemoveHooks = props && props.onVnodeBeforeRemove
|
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
hostRemove(vnode.el!)
|
hostRemove(vnode.el!)
|
||||||
if (anchor != null) hostRemove(anchor)
|
if (anchor != null) hostRemove(anchor)
|
||||||
const removedHook = props && props.onVnodeRemoved
|
if (transition != null && transition.afterLeave) {
|
||||||
removedHook && removedHook()
|
transition.afterLeave()
|
||||||
}
|
|
||||||
if (vnode.shapeFlag & ShapeFlags.ELEMENT && beforeRemoveHooks != null) {
|
|
||||||
const delayLeave = props && props.onVnodeDelayLeave
|
|
||||||
const performLeave = () => {
|
|
||||||
invokeBeforeRemoveHooks(
|
|
||||||
beforeRemoveHooks,
|
|
||||||
parentComponent,
|
|
||||||
vnode,
|
|
||||||
remove
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (vnode.shapeFlag & ShapeFlags.ELEMENT && transition != null) {
|
||||||
|
const { leave, delayLeave } = transition
|
||||||
|
const performLeave = () => leave(el!, remove)
|
||||||
if (delayLeave) {
|
if (delayLeave) {
|
||||||
delayLeave(performLeave)
|
delayLeave(performLeave)
|
||||||
} else {
|
} 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(
|
function unmountComponent(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
parentSuspense: HostSuspenseBoundary | null,
|
parentSuspense: HostSuspenseBoundary | null,
|
||||||
|
@ -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 { TransitionProps } from './components/Transition'
|
import { TransitionData } 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
|
||||||
@ -57,11 +57,6 @@ export interface VNodeProps {
|
|||||||
onVnodeUpdated?: (vnode: VNode, oldVNode: VNode) => void
|
onVnodeUpdated?: (vnode: VNode, oldVNode: VNode) => void
|
||||||
onVnodeBeforeUnmount?: (vnode: VNode) => void
|
onVnodeBeforeUnmount?: (vnode: VNode) => void
|
||||||
onVnodeUnmounted?: (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> =
|
type VNodeChildAtom<HostNode, HostElement> =
|
||||||
@ -98,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: TransitionProps | null
|
transition: TransitionData | null
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
|
Loading…
Reference in New Issue
Block a user