refactor: reuse code from BaseTransition

This commit is contained in:
daiwei 2025-02-28 09:45:04 +08:00
parent 7cee02438f
commit a8140ac826
3 changed files with 129 additions and 30 deletions

View File

@ -25,7 +25,7 @@ import { SchedulerJobFlags } from '../scheduler'
type Hook<T = () => void> = T | T[] type Hook<T = () => void> = T | T[]
const leaveCbKey: unique symbol = Symbol('_leaveCb') export const leaveCbKey: unique symbol = Symbol('_leaveCb')
const enterCbKey: unique symbol = Symbol('_enterCb') const enterCbKey: unique symbol = Symbol('_enterCb')
export interface BaseTransitionProps<HostElement = RendererElement> { export interface BaseTransitionProps<HostElement = RendererElement> {
@ -88,7 +88,7 @@ export interface TransitionState {
isUnmounting: boolean isUnmounting: boolean
// Track pending leave callbacks for children of the same key. // Track pending leave callbacks for children of the same key.
// This is used to force remove leaving a child when a new copy is entering. // This is used to force remove leaving a child when a new copy is entering.
leavingVNodes: Map<any, Record<string, VNode>> leavingVNodes: Map<any, Record<string, any>>
} }
export interface TransitionElement { export interface TransitionElement {
@ -319,6 +319,13 @@ function getLeavingNodesForType(
return leavingVNodesCache return leavingVNodesCache
} }
export interface TransitionHooksContext {
setLeavingNodeCache: () => void
unsetLeavingNodeCache: () => void
earlyRemove: () => void
cloneHooks: (node: any) => TransitionHooks
}
// The transition hooks are attached to the vnode as vnode.transition // The transition hooks are attached to the vnode as vnode.transition
// and will be called at appropriate timing in the renderer. // and will be called at appropriate timing in the renderer.
export function resolveTransitionHooks( export function resolveTransitionHooks(
@ -328,6 +335,57 @@ export function resolveTransitionHooks(
instance: GenericComponentInstance, instance: GenericComponentInstance,
postClone?: (hooks: TransitionHooks) => void, postClone?: (hooks: TransitionHooks) => void,
): TransitionHooks { ): TransitionHooks {
const key = String(vnode.key)
const leavingVNodesCache = getLeavingNodesForType(state, vnode)
const context: TransitionHooksContext = {
setLeavingNodeCache: () => {
leavingVNodesCache[key] = vnode
},
unsetLeavingNodeCache: () => {
if (leavingVNodesCache[key] === vnode) {
delete leavingVNodesCache[key]
}
},
earlyRemove: () => {
const leavingVNode = leavingVNodesCache[key]
if (
leavingVNode &&
isSameVNodeType(vnode, leavingVNode) &&
(leavingVNode.el as TransitionElement)[leaveCbKey]
) {
// force early removal (not cancelled)
;(leavingVNode.el as TransitionElement)[leaveCbKey]!()
}
},
cloneHooks: vnode => {
const hooks = resolveTransitionHooks(
vnode,
props,
state,
instance,
postClone,
)
if (postClone) postClone(hooks)
return hooks
},
}
return baseResolveTransitionHooks(context, props, state, instance)
}
export function baseResolveTransitionHooks(
context: TransitionHooksContext,
props: BaseTransitionProps<any>,
state: TransitionState,
instance: GenericComponentInstance,
): TransitionHooks {
const {
setLeavingNodeCache,
unsetLeavingNodeCache,
earlyRemove,
cloneHooks,
} = context
const { const {
appear, appear,
mode, mode,
@ -345,8 +403,6 @@ export function resolveTransitionHooks(
onAfterAppear, onAfterAppear,
onAppearCancelled, onAppearCancelled,
} = props } = props
const key = String(vnode.key)
const leavingVNodesCache = getLeavingNodesForType(state, vnode)
const callHook: TransitionHookCaller = (hook, args) => { const callHook: TransitionHookCaller = (hook, args) => {
hook && hook &&
@ -388,16 +444,7 @@ export function resolveTransitionHooks(
el[leaveCbKey](true /* cancelled */) el[leaveCbKey](true /* cancelled */)
} }
// for toggled element with same key (v-if) // for toggled element with same key (v-if)
const leavingVNode = leavingVNodesCache[key] earlyRemove()
if (
leavingVNode &&
isSameVNodeType(vnode, leavingVNode) &&
// TODO refactor
((leavingVNode.el || leavingVNode) as TransitionElement)[leaveCbKey]
) {
// force early removal (not cancelled)
;((leavingVNode.el || leavingVNode) as TransitionElement)[leaveCbKey]!()
}
callHook(hook, [el]) callHook(hook, [el])
}, },
@ -436,7 +483,7 @@ export function resolveTransitionHooks(
}, },
leave(el, remove) { leave(el, remove) {
const key = String(vnode.key) // const key = String(vnode.key)
if (el[enterCbKey]) { if (el[enterCbKey]) {
el[enterCbKey](true /* cancelled */) el[enterCbKey](true /* cancelled */)
} }
@ -455,11 +502,9 @@ export function resolveTransitionHooks(
callHook(onAfterLeave, [el]) callHook(onAfterLeave, [el])
} }
el[leaveCbKey] = undefined el[leaveCbKey] = undefined
if (leavingVNodesCache[key] === vnode) { unsetLeavingNodeCache()
delete leavingVNodesCache[key]
}
}) })
leavingVNodesCache[key] = vnode setLeavingNodeCache()
if (onLeave) { if (onLeave) {
callAsyncHook(onLeave, [el, done]) callAsyncHook(onLeave, [el, done])
} else { } else {
@ -467,16 +512,8 @@ export function resolveTransitionHooks(
} }
}, },
clone(vnode) { clone(node) {
const hooks = resolveTransitionHooks( return cloneHooks(node)
vnode,
props,
state,
instance,
postClone,
)
if (postClone) postClone(hooks)
return hooks
}, },
} }

View File

@ -150,8 +150,10 @@ export { registerRuntimeCompiler, isRuntimeOnly } from './component'
export { export {
useTransitionState, useTransitionState,
resolveTransitionHooks, resolveTransitionHooks,
baseResolveTransitionHooks,
setTransitionHooks, setTransitionHooks,
getTransitionRawChildren, getTransitionRawChildren,
leaveCbKey,
} from './components/BaseTransition' } from './components/BaseTransition'
export { initCustomFormatter } from './customFormatter' export { initCustomFormatter } from './customFormatter'
@ -335,6 +337,8 @@ export type { SuspenseBoundary } from './components/Suspense'
export type { export type {
TransitionState, TransitionState,
TransitionHooks, TransitionHooks,
TransitionHooksContext,
TransitionElement,
} from './components/BaseTransition' } from './components/BaseTransition'
export type { export type {
AsyncComponentOptions, AsyncComponentOptions,

View File

@ -1,10 +1,15 @@
import { import {
type GenericComponentInstance,
type TransitionElement,
type TransitionHooks, type TransitionHooks,
type TransitionHooksContext,
type TransitionProps, type TransitionProps,
type TransitionState,
type VaporTransitionInterface, type VaporTransitionInterface,
baseResolveTransitionHooks,
currentInstance, currentInstance,
leaveCbKey,
registerVaporTransition, registerVaporTransition,
resolveTransitionHooks,
useTransitionState, useTransitionState,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import type { Block } from '../block' import type { Block } from '../block'
@ -64,6 +69,59 @@ export const vaporTransitionImpl: VaporTransitionInterface = {
}, },
} }
function resolveTransitionHooks(
block: Block & { key: string },
props: TransitionProps,
state: TransitionState,
instance: GenericComponentInstance,
postClone?: (hooks: TransitionHooks) => void,
): TransitionHooks {
const key = String(block.key)
const leavingNodeCache = getLeavingNodesForBlock(state, block)
const context: TransitionHooksContext = {
setLeavingNodeCache: () => {
leavingNodeCache[key] = block
},
unsetLeavingNodeCache: () => {
if (leavingNodeCache[key] === block) {
delete leavingNodeCache[key]
}
},
earlyRemove: () => {
const leavingNode = leavingNodeCache[key]
if (leavingNode && (leavingNode as TransitionElement)[leaveCbKey]) {
// force early removal (not cancelled)
;(leavingNode as TransitionElement)[leaveCbKey]!()
}
},
cloneHooks: block => {
const hooks = resolveTransitionHooks(
block,
props,
state,
instance,
postClone,
)
if (postClone) postClone(hooks)
return hooks
},
}
return baseResolveTransitionHooks(context, props, state, instance)
}
function getLeavingNodesForBlock(
state: TransitionState,
block: Block,
): Record<string, Block> {
const { leavingVNodes } = state
let leavingNodesCache = leavingVNodes.get(block)!
if (!leavingNodesCache) {
leavingNodesCache = Object.create(null)
leavingVNodes.set(block, leavingNodesCache)
}
return leavingNodesCache
}
function setTransitionHooks(block: Block, hooks: TransitionHooks) { function setTransitionHooks(block: Block, hooks: TransitionHooks) {
if (isVaporComponent(block)) { if (isVaporComponent(block)) {
setTransitionHooks(block.block, hooks) setTransitionHooks(block.block, hooks)