feat(transition): base transition component
This commit is contained in:
parent
f7009d58a3
commit
93561b080e
@ -39,6 +39,9 @@ export interface KeepAliveSink {
|
|||||||
deactivate: (vnode: VNode) => void
|
deactivate: (vnode: VNode) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isKeepAlive = (vnode: VNode): boolean =>
|
||||||
|
(vnode.type as any).__isKeepAlive
|
||||||
|
|
||||||
const KeepAliveImpl = {
|
const KeepAliveImpl = {
|
||||||
name: `KeepAlive`,
|
name: `KeepAlive`,
|
||||||
|
|
||||||
@ -47,6 +50,12 @@ const KeepAliveImpl = {
|
|||||||
// would prevent it from being tree-shaken.
|
// would prevent it from being tree-shaken.
|
||||||
__isKeepAlive: true,
|
__isKeepAlive: true,
|
||||||
|
|
||||||
|
props: {
|
||||||
|
include: [String, RegExp, Array],
|
||||||
|
exclude: [String, RegExp, Array],
|
||||||
|
max: [String, Number]
|
||||||
|
},
|
||||||
|
|
||||||
setup(props: KeepAliveProps, { slots }: SetupContext) {
|
setup(props: KeepAliveProps, { slots }: SetupContext) {
|
||||||
const cache: Cache = new Map()
|
const cache: Cache = new Map()
|
||||||
const keys: Keys = new Set()
|
const keys: Keys = new Set()
|
||||||
@ -200,14 +209,6 @@ const KeepAliveImpl = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__) {
|
|
||||||
;(KeepAliveImpl as any).props = {
|
|
||||||
include: [String, RegExp, Array],
|
|
||||||
exclude: [String, RegExp, Array],
|
|
||||||
max: [String, Number]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// export the public type for h/tsx inference
|
// export the public type for h/tsx inference
|
||||||
export const KeepAlive = (KeepAliveImpl as any) as {
|
export const KeepAlive = (KeepAliveImpl as any) as {
|
||||||
new (): {
|
new (): {
|
||||||
|
199
packages/runtime-core/src/components/Transition.ts
Normal file
199
packages/runtime-core/src/components/Transition.ts
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { createComponent } from '../apiCreateComponent'
|
||||||
|
import { getCurrentInstance } from '../component'
|
||||||
|
import {
|
||||||
|
cloneVNode,
|
||||||
|
Comment,
|
||||||
|
isSameVNodeType,
|
||||||
|
VNodeProps,
|
||||||
|
VNode,
|
||||||
|
mergeProps
|
||||||
|
} from '../vnode'
|
||||||
|
import { warn } from '../warning'
|
||||||
|
import { isKeepAlive } from './KeepAlive'
|
||||||
|
import { toRaw } from '@vue/reactivity'
|
||||||
|
import { onMounted } from '../apiLifecycle'
|
||||||
|
|
||||||
|
// Using camel case here makes it easier to use in render functions & JSX.
|
||||||
|
// In templates these will be written as @before-enter="xxx"
|
||||||
|
// The compiler has special handling to convert them into the proper cases.
|
||||||
|
export interface TransitionProps {
|
||||||
|
mode?: 'in-out' | 'out-in' | 'default'
|
||||||
|
appear?: boolean
|
||||||
|
// enter
|
||||||
|
onBeforeEnter?: (el: any) => void
|
||||||
|
onEnter?: (el: any, done: () => void) => void
|
||||||
|
onAfterEnter?: (el: any) => void
|
||||||
|
onEnterCancelled?: (el: any) => void
|
||||||
|
// leave
|
||||||
|
onBeforeLeave?: (el: any) => void
|
||||||
|
onLeave?: (el: any, done: () => void) => void
|
||||||
|
onAfterLeave?: (el: any) => void
|
||||||
|
onLeaveCancelled?: (el: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Transition = createComponent({
|
||||||
|
name: `Transition`,
|
||||||
|
setup(props: TransitionProps, { slots }) {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
let isLeaving = false
|
||||||
|
let isMounted = false
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
isMounted = true
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const children = slots.default && slots.default()
|
||||||
|
if (!children || !children.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// warn multiple elements
|
||||||
|
if (__DEV__ && children.length > 1) {
|
||||||
|
warn(
|
||||||
|
'<transition> can only be used on a single element. Use ' +
|
||||||
|
'<transition-group> for lists.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// there's no need to track reactivity for these props so use the raw
|
||||||
|
// props for a bit better perf
|
||||||
|
const rawProps = toRaw(props)
|
||||||
|
const { mode } = rawProps
|
||||||
|
// check mode
|
||||||
|
if (__DEV__ && mode && !['in-out', 'out-in', 'default'].includes(mode)) {
|
||||||
|
warn(`invalid <transition> mode: ${mode}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point children has a guaranteed length of 1.
|
||||||
|
const rawChild = children[0]
|
||||||
|
if (isLeaving) {
|
||||||
|
return placeholder(rawChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawChild.transition = rawProps
|
||||||
|
// 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) &&
|
||||||
|
oldChild.type !== Comment
|
||||||
|
) {
|
||||||
|
// update old tree's hooks in case of dynamic transition
|
||||||
|
oldChild.transition = rawProps
|
||||||
|
// 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)
|
||||||
|
} else if (mode === 'in-out') {
|
||||||
|
let delayedLeave: () => void
|
||||||
|
performDelayedLeave = () => delayedLeave()
|
||||||
|
oldChild.props = mergeProps(oldChild.props!, {
|
||||||
|
onVnodeDelayLeave(performLeave) {
|
||||||
|
delayedLeave = performLeave
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloneVNode(
|
||||||
|
rawChild,
|
||||||
|
resolveTransitionInjections(rawProps, isMounted, performDelayedLeave)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
;(Transition as any).props = {
|
||||||
|
mode: String,
|
||||||
|
appear: Boolean,
|
||||||
|
// enter
|
||||||
|
onBeforeEnter: Function,
|
||||||
|
onEnter: Function,
|
||||||
|
onAfterEnter: Function,
|
||||||
|
onEnterCancelled: Function,
|
||||||
|
// leave
|
||||||
|
onBeforeLeave: Function,
|
||||||
|
onLeave: Function,
|
||||||
|
onAfterLeave: Function,
|
||||||
|
onLeaveCancelled: Function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTransitionInjections(
|
||||||
|
{
|
||||||
|
appear,
|
||||||
|
onBeforeEnter,
|
||||||
|
onEnter,
|
||||||
|
onAfterEnter,
|
||||||
|
onEnterCancelled,
|
||||||
|
onBeforeLeave,
|
||||||
|
onLeave,
|
||||||
|
onAfterLeave,
|
||||||
|
onLeaveCancelled
|
||||||
|
}: TransitionProps,
|
||||||
|
isMounted: boolean,
|
||||||
|
performDelayedLeave?: () => void
|
||||||
|
): VNodeProps {
|
||||||
|
// TODO handle appear
|
||||||
|
// TODO handle cancel hooks
|
||||||
|
return {
|
||||||
|
onVnodeBeforeMount(vnode) {
|
||||||
|
if (!isMounted && !appear) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onBeforeEnter && onBeforeEnter(vnode.el)
|
||||||
|
},
|
||||||
|
onVnodeMounted({ el }) {
|
||||||
|
if (!isMounted && !appear) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const done = () => {
|
||||||
|
onAfterEnter && onAfterEnter(el)
|
||||||
|
performDelayedLeave && performDelayedLeave()
|
||||||
|
}
|
||||||
|
if (onEnter) {
|
||||||
|
onEnter(el, done)
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onVnodeBeforeRemove({ el }, remove) {
|
||||||
|
onBeforeLeave && onBeforeLeave(el)
|
||||||
|
if (onLeave) {
|
||||||
|
onLeave(el, () => {
|
||||||
|
remove()
|
||||||
|
onAfterLeave && onAfterLeave(el)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
remove()
|
||||||
|
onAfterLeave && onAfterLeave(el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the placeholder really only handles one special case: KeepAlive
|
||||||
|
// in the case of a KeepAlive in a leave phase we need to return a KeepAlive
|
||||||
|
// placeholder with empty content to avoid the KeepAlive instance from being
|
||||||
|
// unmounted.
|
||||||
|
function placeholder(vnode: VNode): VNode | undefined {
|
||||||
|
if (isKeepAlive(vnode)) {
|
||||||
|
vnode = cloneVNode(vnode)
|
||||||
|
vnode.children = null
|
||||||
|
return vnode
|
||||||
|
}
|
||||||
|
}
|
@ -147,7 +147,7 @@ export function withDirectives<T extends VNode>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function invokeDirectiveHook(
|
export function invokeDirectiveHook(
|
||||||
hook: Function | Function[],
|
hook: ((...args: any[]) => any) | ((...args: any[]) => any)[],
|
||||||
instance: ComponentInternalInstance | null,
|
instance: ComponentInternalInstance | null,
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
prevVNode: VNode | null = null
|
prevVNode: VNode | null = null
|
||||||
|
@ -28,6 +28,7 @@ export { Text, Comment, Fragment, Portal } from './vnode'
|
|||||||
// Internal Components
|
// Internal Components
|
||||||
export { Suspense, SuspenseProps } from './components/Suspense'
|
export { Suspense, SuspenseProps } from './components/Suspense'
|
||||||
export { KeepAlive, KeepAliveProps } from './components/KeepAlive'
|
export { KeepAlive, KeepAliveProps } from './components/KeepAlive'
|
||||||
|
export { Transition, TransitionProps } from './components/Transition'
|
||||||
// VNode flags
|
// VNode flags
|
||||||
export { PublicShapeFlags as ShapeFlags } from './shapeFlags'
|
export { PublicShapeFlags as ShapeFlags } from './shapeFlags'
|
||||||
import { PublicPatchFlags } from '@vue/shared'
|
import { PublicPatchFlags } from '@vue/shared'
|
||||||
|
@ -6,7 +6,8 @@ import {
|
|||||||
normalizeVNode,
|
normalizeVNode,
|
||||||
VNode,
|
VNode,
|
||||||
VNodeChildren,
|
VNodeChildren,
|
||||||
createVNode
|
createVNode,
|
||||||
|
isSameVNodeType
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
@ -26,7 +27,8 @@ 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 {
|
||||||
@ -50,8 +52,12 @@ import {
|
|||||||
queueEffectWithSuspense,
|
queueEffectWithSuspense,
|
||||||
SuspenseImpl
|
SuspenseImpl
|
||||||
} from './components/Suspense'
|
} from './components/Suspense'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import {
|
||||||
import { KeepAliveSink } from './components/KeepAlive'
|
ErrorCodes,
|
||||||
|
callWithErrorHandling,
|
||||||
|
callWithAsyncErrorHandling
|
||||||
|
} from './errorHandling'
|
||||||
|
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
|
||||||
|
|
||||||
export interface RendererOptions<HostNode = any, HostElement = any> {
|
export interface RendererOptions<HostNode = any, HostElement = any> {
|
||||||
patchProp(
|
patchProp(
|
||||||
@ -128,10 +134,6 @@ function createDevEffectOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSameType(n1: VNode, n2: VNode): boolean {
|
|
||||||
return n1.type === n2.type && n1.key === n2.key
|
|
||||||
}
|
|
||||||
|
|
||||||
export function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
|
export function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
|
||||||
for (let i = 0; i < hooks.length; i++) {
|
for (let i = 0; i < hooks.length; i++) {
|
||||||
hooks[i](arg)
|
hooks[i](arg)
|
||||||
@ -203,7 +205,7 @@ export function createRenderer<
|
|||||||
optimized: boolean = false
|
optimized: boolean = false
|
||||||
) {
|
) {
|
||||||
// patching & not same type, unmount old tree
|
// patching & not same type, unmount old tree
|
||||||
if (n1 != null && !isSameType(n1, n2)) {
|
if (n1 != null && !isSameVNodeType(n1, n2)) {
|
||||||
anchor = getNextHostNode(n1)
|
anchor = getNextHostNode(n1)
|
||||||
unmount(n1, parentComponent, parentSuspense, true)
|
unmount(n1, parentComponent, parentSuspense, true)
|
||||||
n1 = null
|
n1 = null
|
||||||
@ -386,7 +388,7 @@ export function createRenderer<
|
|||||||
hostInsert(el, container, anchor)
|
hostInsert(el, container, anchor)
|
||||||
if (props != null && props.onVnodeMounted != null) {
|
if (props != null && props.onVnodeMounted != null) {
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
invokeDirectiveHook(props.onVnodeMounted, parentComponent, vnode)
|
invokeDirectiveHook(props.onVnodeMounted!, parentComponent, vnode)
|
||||||
}, parentSuspense)
|
}, parentSuspense)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -844,7 +846,7 @@ export function createRenderer<
|
|||||||
const Comp = initialVNode.type as Component
|
const Comp = initialVNode.type as Component
|
||||||
|
|
||||||
// inject renderer internals for keepAlive
|
// inject renderer internals for keepAlive
|
||||||
if ((Comp as any).__isKeepAlive) {
|
if (isKeepAlive(initialVNode)) {
|
||||||
const sink = instance.sink as KeepAliveSink
|
const sink = instance.sink as KeepAliveSink
|
||||||
sink.renderer = internals
|
sink.renderer = internals
|
||||||
sink.parentSuspense = parentSuspense
|
sink.parentSuspense = parentSuspense
|
||||||
@ -937,8 +939,9 @@ export function createRenderer<
|
|||||||
if (next !== null) {
|
if (next !== null) {
|
||||||
updateComponentPreRender(instance, next)
|
updateComponentPreRender(instance, next)
|
||||||
}
|
}
|
||||||
|
const nextTree = renderComponentRoot(instance)
|
||||||
const prevTree = instance.subTree
|
const prevTree = instance.subTree
|
||||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
instance.subTree = nextTree
|
||||||
// beforeUpdate hook
|
// beforeUpdate hook
|
||||||
if (instance.bu !== null) {
|
if (instance.bu !== null) {
|
||||||
invokeHooks(instance.bu)
|
invokeHooks(instance.bu)
|
||||||
@ -1167,7 +1170,7 @@ export function createRenderer<
|
|||||||
const n2 = optimized
|
const n2 = optimized
|
||||||
? (c2[i] as HostVNode)
|
? (c2[i] as HostVNode)
|
||||||
: (c2[i] = normalizeVNode(c2[i]))
|
: (c2[i] = normalizeVNode(c2[i]))
|
||||||
if (isSameType(n1, n2)) {
|
if (isSameVNodeType(n1, n2)) {
|
||||||
patch(
|
patch(
|
||||||
n1,
|
n1,
|
||||||
n2,
|
n2,
|
||||||
@ -1192,7 +1195,7 @@ export function createRenderer<
|
|||||||
const n2 = optimized
|
const n2 = optimized
|
||||||
? (c2[i] as HostVNode)
|
? (c2[i] as HostVNode)
|
||||||
: (c2[e2] = normalizeVNode(c2[e2]))
|
: (c2[e2] = normalizeVNode(c2[e2]))
|
||||||
if (isSameType(n1, n2)) {
|
if (isSameVNodeType(n1, n2)) {
|
||||||
patch(
|
patch(
|
||||||
n1,
|
n1,
|
||||||
n2,
|
n2,
|
||||||
@ -1308,7 +1311,7 @@ export function createRenderer<
|
|||||||
for (j = s2; j <= e2; j++) {
|
for (j = s2; j <= e2; j++) {
|
||||||
if (
|
if (
|
||||||
newIndexToOldIndexMap[j - s2] === 0 &&
|
newIndexToOldIndexMap[j - s2] === 0 &&
|
||||||
isSameType(prevChild, c2[j] as HostVNode)
|
isSameVNodeType(prevChild, c2[j] as HostVNode)
|
||||||
) {
|
) {
|
||||||
newIndex = j
|
newIndex = j
|
||||||
break
|
break
|
||||||
@ -1459,17 +1462,71 @@ export function createRenderer<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (doRemove) {
|
if (doRemove) {
|
||||||
hostRemove(vnode.el!)
|
const beforeRemoveHooks = props && props.onVnodeBeforeRemove
|
||||||
if (anchor != null) hostRemove(anchor)
|
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 (delayLeave) {
|
||||||
|
delayLeave(performLeave)
|
||||||
|
} else {
|
||||||
|
performLeave()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remove()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props != null && props.onVnodeUnmounted != null) {
|
if (props != null && props.onVnodeUnmounted != null) {
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
invokeDirectiveHook(props.onVnodeUnmounted, parentComponent, vnode)
|
invokeDirectiveHook(props.onVnodeUnmounted!, parentComponent, vnode)
|
||||||
}, parentSuspense)
|
}, parentSuspense)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,6 +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'
|
||||||
|
|
||||||
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
||||||
__isFragment: true
|
__isFragment: true
|
||||||
@ -48,6 +49,19 @@ export interface VNodeProps {
|
|||||||
[key: string]: any
|
[key: string]: any
|
||||||
key?: string | number
|
key?: string | number
|
||||||
ref?: string | Ref | ((ref: object | null) => void)
|
ref?: string | Ref | ((ref: object | null) => void)
|
||||||
|
|
||||||
|
// vnode hooks
|
||||||
|
onVnodeBeforeMount?: (vnode: VNode) => void
|
||||||
|
onVnodeMounted?: (vnode: VNode) => void
|
||||||
|
onVnodeBeforeUpdate?: (vnode: VNode, oldVNode: VNode) => void
|
||||||
|
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> =
|
type VNodeChildAtom<HostNode, HostElement> =
|
||||||
@ -79,11 +93,12 @@ export interface VNode<HostNode = any, HostElement = any> {
|
|||||||
type: VNodeTypes
|
type: VNodeTypes
|
||||||
props: VNodeProps | null
|
props: VNodeProps | null
|
||||||
key: string | number | null
|
key: string | number | null
|
||||||
ref: string | Function | null
|
ref: string | Ref | ((ref: object | null) => void) | null
|
||||||
children: NormalizedChildren<HostNode, HostElement>
|
children: NormalizedChildren<HostNode, HostElement>
|
||||||
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
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
@ -173,9 +188,13 @@ export function isVNode(value: any): value is VNode {
|
|||||||
return value ? value._isVNode === true : false
|
return value ? value._isVNode === true : false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
|
||||||
|
return n1.type === n2.type && n1.key === n2.key
|
||||||
|
}
|
||||||
|
|
||||||
export function createVNode(
|
export function createVNode(
|
||||||
type: VNodeTypes,
|
type: VNodeTypes,
|
||||||
props: { [key: string]: any } | null = null,
|
props: (Data & VNodeProps) | null = null,
|
||||||
children: unknown = null,
|
children: unknown = null,
|
||||||
patchFlag: number = 0,
|
patchFlag: number = 0,
|
||||||
dynamicProps: string[] | null = null
|
dynamicProps: string[] | null = null
|
||||||
@ -221,6 +240,7 @@ export function createVNode(
|
|||||||
component: null,
|
component: null,
|
||||||
suspense: null,
|
suspense: null,
|
||||||
dirs: null,
|
dirs: null,
|
||||||
|
transition: null,
|
||||||
el: null,
|
el: null,
|
||||||
anchor: null,
|
anchor: null,
|
||||||
target: null,
|
target: null,
|
||||||
@ -252,7 +272,7 @@ export function createVNode(
|
|||||||
|
|
||||||
export function cloneVNode<T, U>(
|
export function cloneVNode<T, U>(
|
||||||
vnode: VNode<T, U>,
|
vnode: VNode<T, U>,
|
||||||
extraProps?: Data
|
extraProps?: Data & VNodeProps
|
||||||
): VNode<T, U> {
|
): VNode<T, U> {
|
||||||
// This is intentionally NOT using spread or extend to avoid the runtime
|
// This is intentionally NOT using spread or extend to avoid the runtime
|
||||||
// key enumeration cost.
|
// key enumeration cost.
|
||||||
@ -274,6 +294,7 @@ export function cloneVNode<T, U>(
|
|||||||
dynamicChildren: vnode.dynamicChildren,
|
dynamicChildren: vnode.dynamicChildren,
|
||||||
appContext: vnode.appContext,
|
appContext: vnode.appContext,
|
||||||
dirs: vnode.dirs,
|
dirs: vnode.dirs,
|
||||||
|
transition: vnode.transition,
|
||||||
|
|
||||||
// These should technically only be non-null on mounted VNodes. However,
|
// These should technically only be non-null on mounted VNodes. However,
|
||||||
// they *should* be copied for kept-alive vnodes. So we just always copy
|
// they *should* be copied for kept-alive vnodes. So we just always copy
|
||||||
@ -376,7 +397,7 @@ export function normalizeClass(value: unknown): string {
|
|||||||
|
|
||||||
const handlersRE = /^on|^vnode/
|
const handlersRE = /^on|^vnode/
|
||||||
|
|
||||||
export function mergeProps(...args: Data[]) {
|
export function mergeProps(...args: (Data & VNodeProps)[]) {
|
||||||
const ret: Data = {}
|
const ret: Data = {}
|
||||||
extend(ret, args[0])
|
extend(ret, args[0])
|
||||||
for (let i = 1; i < args.length; i++) {
|
for (let i = 1; i < args.length; i++) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user