mirror of https://github.com/vuejs/core.git
feat(transition): compat with keep-alive
This commit is contained in:
parent
16ea2993d6
commit
c6fb506fc0
|
@ -347,12 +347,8 @@ describe('compiler: element transform', () => {
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node.arguments).toMatchObject([
|
||||||
KEEP_ALIVE,
|
KEEP_ALIVE,
|
||||||
`null`,
|
`null`,
|
||||||
createObjectMatcher({
|
// keep-alive should not compile content to slots
|
||||||
default: {
|
[{ type: NodeTypes.ELEMENT, tag: 'span' }]
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION
|
|
||||||
},
|
|
||||||
_compiled: `[true]`
|
|
||||||
})
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,8 +138,11 @@ export const transformElement: NodeTransform = (node, context) => {
|
||||||
if (!hasProps) {
|
if (!hasProps) {
|
||||||
args.push(`null`)
|
args.push(`null`)
|
||||||
}
|
}
|
||||||
// Portal should have normal children instead of slots
|
// Portal & KeepAlive should have normal children instead of slots
|
||||||
if (isComponent && !isPortal) {
|
// Portal is not a real component has dedicated handling in the renderer
|
||||||
|
// KeepAlive should not track its own deps so that it can be used inside
|
||||||
|
// Transition
|
||||||
|
if (isComponent && !isPortal && !isKeepAlive) {
|
||||||
const { slots, hasDynamicSlots } = buildSlots(node, context)
|
const { slots, hasDynamicSlots } = buildSlots(node, context)
|
||||||
args.push(slots)
|
args.push(slots)
|
||||||
if (hasDynamicSlots) {
|
if (hasDynamicSlots) {
|
||||||
|
|
|
@ -91,7 +91,8 @@ export function renderComponentRoot(
|
||||||
if (
|
if (
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
!(result.shapeFlag & ShapeFlags.COMPONENT) &&
|
!(result.shapeFlag & ShapeFlags.COMPONENT) &&
|
||||||
!(result.shapeFlag & ShapeFlags.ELEMENT)
|
!(result.shapeFlag & ShapeFlags.ELEMENT) &&
|
||||||
|
result.type !== Comment
|
||||||
) {
|
) {
|
||||||
warn(
|
warn(
|
||||||
`Component inside <Transition> renders non-element root node ` +
|
`Component inside <Transition> renders non-element root node ` +
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { ComponentInternalInstance, currentInstance } from './component'
|
import { ComponentInternalInstance, currentInstance } from './component'
|
||||||
import { VNode, NormalizedChildren, normalizeVNode, VNodeChild } from './vnode'
|
import { VNode, NormalizedChildren, normalizeVNode, VNodeChild } from './vnode'
|
||||||
import { isArray, isFunction } from '@vue/shared'
|
import { isArray, isFunction, EMPTY_OBJ } from '@vue/shared'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
import { isKeepAlive } from './components/KeepAlive'
|
||||||
|
|
||||||
export type Slot = (...args: any[]) => VNode[]
|
export type Slot = (...args: any[]) => VNode[]
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ export function resolveSlots(
|
||||||
}
|
}
|
||||||
} else if (children !== null) {
|
} else if (children !== null) {
|
||||||
// non slot object children (direct value) passed to a component
|
// non slot object children (direct value) passed to a component
|
||||||
if (__DEV__) {
|
if (__DEV__ && !isKeepAlive(instance.vnode)) {
|
||||||
warn(
|
warn(
|
||||||
`Non-function value encountered for default slot. ` +
|
`Non-function value encountered for default slot. ` +
|
||||||
`Prefer function slots for better performance.`
|
`Prefer function slots for better performance.`
|
||||||
|
@ -74,7 +75,5 @@ export function resolveSlots(
|
||||||
const normalized = normalizeSlotValue(children)
|
const normalized = normalizeSlotValue(children)
|
||||||
slots = { default: () => normalized }
|
slots = { default: () => normalized }
|
||||||
}
|
}
|
||||||
if (slots !== void 0) {
|
instance.slots = slots || EMPTY_OBJ
|
||||||
instance.slots = slots
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,13 @@ import {
|
||||||
SetupContext,
|
SetupContext,
|
||||||
ComponentOptions
|
ComponentOptions
|
||||||
} from '../component'
|
} from '../component'
|
||||||
import { cloneVNode, Comment, isSameVNodeType, VNode } from '../vnode'
|
import {
|
||||||
|
cloneVNode,
|
||||||
|
Comment,
|
||||||
|
isSameVNodeType,
|
||||||
|
VNode,
|
||||||
|
VNodeChildren
|
||||||
|
} 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'
|
||||||
|
@ -36,17 +42,38 @@ export interface BaseTransitionProps {
|
||||||
onLeaveCancelled?: (el: any) => void
|
onLeaveCancelled?: (el: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TransitionHooks {
|
||||||
|
persisted: boolean
|
||||||
|
beforeEnter(el: object): void
|
||||||
|
enter(el: object): void
|
||||||
|
leave(el: object, remove: () => void): void
|
||||||
|
afterLeave?(): void
|
||||||
|
delayLeave?(delayedLeave: () => void): void
|
||||||
|
delayedLeave?(): void
|
||||||
|
}
|
||||||
|
|
||||||
type TransitionHookCaller = (
|
type TransitionHookCaller = (
|
||||||
hook: ((el: any) => void) | undefined,
|
hook: ((el: any) => void) | undefined,
|
||||||
args?: any[]
|
args?: any[]
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
|
type PendingCallback = (cancelled?: boolean) => void
|
||||||
|
|
||||||
interface TransitionState {
|
interface TransitionState {
|
||||||
isMounted: boolean
|
isMounted: boolean
|
||||||
isLeaving: boolean
|
isLeaving: boolean
|
||||||
isUnmounting: boolean
|
isUnmounting: boolean
|
||||||
pendingEnter?: (cancelled?: boolean) => void
|
// Track pending leave callbacks for children of the same key.
|
||||||
pendingLeave?: (cancelled?: boolean) => void
|
// This is used to force remove leaving a child when a new copy is entering.
|
||||||
|
leavingVNodes: Record<string, VNode>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TransitionElement {
|
||||||
|
// in persisted mode (e.g. v-show), the same element is toggled, so the
|
||||||
|
// pending enter/leave callbacks may need to cancalled if the state is toggled
|
||||||
|
// before it finishes.
|
||||||
|
_enterCb?: PendingCallback
|
||||||
|
_leaveCb?: PendingCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseTransitionImpl = {
|
const BaseTransitionImpl = {
|
||||||
|
@ -56,7 +83,8 @@ const BaseTransitionImpl = {
|
||||||
const state: TransitionState = {
|
const state: TransitionState = {
|
||||||
isMounted: false,
|
isMounted: false,
|
||||||
isLeaving: false,
|
isLeaving: false,
|
||||||
isUnmounting: false
|
isUnmounting: false,
|
||||||
|
leavingVNodes: Object.create(null)
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
state.isMounted = true
|
state.isMounted = true
|
||||||
|
@ -84,7 +112,7 @@ const BaseTransitionImpl = {
|
||||||
// warn multiple elements
|
// warn multiple elements
|
||||||
if (__DEV__ && children.length > 1) {
|
if (__DEV__ && children.length > 1) {
|
||||||
warn(
|
warn(
|
||||||
'<transition> can only be used on a single element. Use ' +
|
'<transition> can only be used on a single element or component. Use ' +
|
||||||
'<transition-group> for lists.'
|
'<transition-group> for lists.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -101,45 +129,53 @@ const BaseTransitionImpl = {
|
||||||
// at this point children has a guaranteed length of 1.
|
// at this point children has a guaranteed length of 1.
|
||||||
const child = children[0]
|
const child = children[0]
|
||||||
if (state.isLeaving) {
|
if (state.isLeaving) {
|
||||||
return placeholder(child)
|
return emptyPlaceholder(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
let delayedLeave: (() => void) | undefined
|
// in the case of <transition><keep-alive/></transition>, we need to
|
||||||
const performDelayedLeave = () => delayedLeave && delayedLeave()
|
// compare the type of the kept-alive children.
|
||||||
|
const innerChild = getKeepAliveChild(child)
|
||||||
|
if (!innerChild) {
|
||||||
|
return emptyPlaceholder(child)
|
||||||
|
}
|
||||||
|
|
||||||
const transitionHooks = (child.transition = resolveTransitionHooks(
|
const enterHooks = (innerChild.transition = resolveTransitionHooks(
|
||||||
|
innerChild,
|
||||||
rawProps,
|
rawProps,
|
||||||
state,
|
state,
|
||||||
callTransitionHook,
|
callTransitionHook
|
||||||
performDelayedLeave
|
|
||||||
))
|
))
|
||||||
|
|
||||||
// clone old subTree because we need to modify it
|
|
||||||
const oldChild = instance.subTree
|
const oldChild = instance.subTree
|
||||||
? (instance.subTree = cloneVNode(instance.subTree))
|
const oldInnerChild = oldChild && getKeepAliveChild(oldChild)
|
||||||
: null
|
|
||||||
|
|
||||||
// handle mode
|
// handle mode
|
||||||
if (
|
if (
|
||||||
oldChild &&
|
oldInnerChild &&
|
||||||
!isSameVNodeType(child, oldChild) &&
|
oldInnerChild.type !== Comment &&
|
||||||
oldChild.type !== Comment
|
!isSameVNodeType(innerChild, oldInnerChild)
|
||||||
) {
|
) {
|
||||||
|
const prevHooks = oldInnerChild.transition!
|
||||||
|
const leavingHooks = resolveTransitionHooks(
|
||||||
|
oldInnerChild,
|
||||||
|
rawProps,
|
||||||
|
state,
|
||||||
|
callTransitionHook
|
||||||
|
)
|
||||||
// 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
|
setTransitionHooks(oldInnerChild, leavingHooks)
|
||||||
updateHOCTransitionData(oldChild, transitionHooks)
|
|
||||||
// switching between different views
|
// switching between different views
|
||||||
if (mode === 'out-in') {
|
if (mode === 'out-in') {
|
||||||
state.isLeaving = true
|
state.isLeaving = true
|
||||||
// return placeholder node and queue update when leave finishes
|
// return placeholder node and queue update when leave finishes
|
||||||
transitionHooks.afterLeave = () => {
|
leavingHooks.afterLeave = () => {
|
||||||
state.isLeaving = false
|
state.isLeaving = false
|
||||||
instance.update()
|
instance.update()
|
||||||
}
|
}
|
||||||
return placeholder(child)
|
return emptyPlaceholder(child)
|
||||||
} else if (mode === 'in-out') {
|
} else if (mode === 'in-out') {
|
||||||
transitionHooks.delayLeave = performLeave => {
|
delete prevHooks.delayedLeave
|
||||||
delayedLeave = performLeave
|
leavingHooks.delayLeave = delayedLeave => {
|
||||||
|
enterHooks.delayedLeave = delayedLeave
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,18 +211,10 @@ export const BaseTransition = (BaseTransitionImpl as any) as {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransitionHooks {
|
|
||||||
persisted: boolean
|
|
||||||
beforeEnter(el: object): void
|
|
||||||
enter(el: object): void
|
|
||||||
leave(el: object, remove: () => void): void
|
|
||||||
afterLeave?(): void
|
|
||||||
delayLeave?(performLeave: () => void): void
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
||||||
function resolveTransitionHooks(
|
function resolveTransitionHooks(
|
||||||
|
vnode: VNode,
|
||||||
{
|
{
|
||||||
appear,
|
appear,
|
||||||
persisted = false,
|
persisted = false,
|
||||||
|
@ -200,36 +228,51 @@ function resolveTransitionHooks(
|
||||||
onLeaveCancelled
|
onLeaveCancelled
|
||||||
}: BaseTransitionProps,
|
}: BaseTransitionProps,
|
||||||
state: TransitionState,
|
state: TransitionState,
|
||||||
callHook: TransitionHookCaller,
|
callHook: TransitionHookCaller
|
||||||
performDelayedLeave: () => void
|
|
||||||
): TransitionHooks {
|
): TransitionHooks {
|
||||||
return {
|
const { leavingVNodes } = state
|
||||||
|
const key = String(vnode.key)
|
||||||
|
|
||||||
|
const hooks: TransitionHooks = {
|
||||||
persisted,
|
persisted,
|
||||||
beforeEnter(el) {
|
beforeEnter(el: TransitionElement) {
|
||||||
if (state.pendingLeave) {
|
|
||||||
state.pendingLeave(true /* cancelled */)
|
|
||||||
}
|
|
||||||
if (!appear && !state.isMounted) {
|
if (!appear && !state.isMounted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// for same element (v-show)
|
||||||
|
if (el._leaveCb) {
|
||||||
|
el._leaveCb(true /* cancelled */)
|
||||||
|
}
|
||||||
|
// for toggled element with same key (v-if)
|
||||||
|
const leavingVNode = leavingVNodes[key]
|
||||||
|
if (
|
||||||
|
leavingVNode &&
|
||||||
|
isSameVNodeType(vnode, leavingVNode) &&
|
||||||
|
leavingVNode.el._leaveCb
|
||||||
|
) {
|
||||||
|
// force early removal (not cancelled)
|
||||||
|
leavingVNode.el._leaveCb()
|
||||||
|
}
|
||||||
callHook(onBeforeEnter, [el])
|
callHook(onBeforeEnter, [el])
|
||||||
},
|
},
|
||||||
|
|
||||||
enter(el) {
|
enter(el: TransitionElement) {
|
||||||
if (!appear && !state.isMounted) {
|
if (!appear && !state.isMounted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let called = false
|
let called = false
|
||||||
const afterEnter = (state.pendingEnter = (cancelled?) => {
|
const afterEnter = (el._enterCb = (cancelled?) => {
|
||||||
if (called) return
|
if (called) return
|
||||||
called = true
|
called = true
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
callHook(onEnterCancelled, [el])
|
callHook(onEnterCancelled, [el])
|
||||||
} else {
|
} else {
|
||||||
callHook(onAfterEnter, [el])
|
callHook(onAfterEnter, [el])
|
||||||
performDelayedLeave()
|
|
||||||
}
|
}
|
||||||
state.pendingEnter = undefined
|
if (hooks.delayedLeave) {
|
||||||
|
hooks.delayedLeave()
|
||||||
|
}
|
||||||
|
el._enterCb = undefined
|
||||||
})
|
})
|
||||||
if (onEnter) {
|
if (onEnter) {
|
||||||
onEnter(el, afterEnter)
|
onEnter(el, afterEnter)
|
||||||
|
@ -238,16 +281,17 @@ function resolveTransitionHooks(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
leave(el, remove) {
|
leave(el: TransitionElement, remove) {
|
||||||
if (state.pendingEnter) {
|
const key = String(vnode.key)
|
||||||
state.pendingEnter(true /* cancelled */)
|
if (el._enterCb) {
|
||||||
|
el._enterCb(true /* cancelled */)
|
||||||
}
|
}
|
||||||
if (state.isUnmounting) {
|
if (state.isUnmounting) {
|
||||||
return remove()
|
return remove()
|
||||||
}
|
}
|
||||||
callHook(onBeforeLeave, [el])
|
callHook(onBeforeLeave, [el])
|
||||||
let called = false
|
let called = false
|
||||||
const afterLeave = (state.pendingLeave = (cancelled?) => {
|
const afterLeave = (el._leaveCb = (cancelled?) => {
|
||||||
if (called) return
|
if (called) return
|
||||||
called = true
|
called = true
|
||||||
remove()
|
remove()
|
||||||
|
@ -256,8 +300,10 @@ function resolveTransitionHooks(
|
||||||
} else {
|
} else {
|
||||||
callHook(onAfterLeave, [el])
|
callHook(onAfterLeave, [el])
|
||||||
}
|
}
|
||||||
state.pendingLeave = undefined
|
el._leaveCb = undefined
|
||||||
|
delete leavingVNodes[key]
|
||||||
})
|
})
|
||||||
|
leavingVNodes[key] = vnode
|
||||||
if (onLeave) {
|
if (onLeave) {
|
||||||
onLeave(el, afterLeave)
|
onLeave(el, afterLeave)
|
||||||
} else {
|
} else {
|
||||||
|
@ -265,13 +311,15 @@ function resolveTransitionHooks(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return hooks
|
||||||
}
|
}
|
||||||
|
|
||||||
// the placeholder really only handles one special case: KeepAlive
|
// 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
|
// 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
|
// placeholder with empty content to avoid the KeepAlive instance from being
|
||||||
// unmounted.
|
// unmounted.
|
||||||
function placeholder(vnode: VNode): VNode | undefined {
|
function emptyPlaceholder(vnode: VNode): VNode | undefined {
|
||||||
if (isKeepAlive(vnode)) {
|
if (isKeepAlive(vnode)) {
|
||||||
vnode = cloneVNode(vnode)
|
vnode = cloneVNode(vnode)
|
||||||
vnode.children = null
|
vnode.children = null
|
||||||
|
@ -279,10 +327,18 @@ function placeholder(vnode: VNode): VNode | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHOCTransitionData(vnode: VNode, data: TransitionHooks) {
|
function getKeepAliveChild(vnode: VNode): VNode | undefined {
|
||||||
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
|
return isKeepAlive(vnode)
|
||||||
updateHOCTransitionData(vnode.component!.subTree, data)
|
? vnode.children
|
||||||
|
? ((vnode.children as VNodeChildren)[0] as VNode)
|
||||||
|
: undefined
|
||||||
|
: vnode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTransitionHooks(vnode: VNode, hooks: TransitionHooks) {
|
||||||
|
if (vnode.shapeFlag & ShapeFlags.COMPONENT && vnode.component) {
|
||||||
|
setTransitionHooks(vnode.component.subTree, hooks)
|
||||||
} else {
|
} else {
|
||||||
vnode.transition = data
|
vnode.transition = hooks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,10 @@ import { SuspenseBoundary } from './Suspense'
|
||||||
import {
|
import {
|
||||||
RendererInternals,
|
RendererInternals,
|
||||||
queuePostRenderEffect,
|
queuePostRenderEffect,
|
||||||
invokeHooks
|
invokeHooks,
|
||||||
|
MoveType
|
||||||
} from '../renderer'
|
} from '../renderer'
|
||||||
|
import { setTransitionHooks } from './BaseTransition'
|
||||||
|
|
||||||
type MatchPattern = string | RegExp | string[] | RegExp[]
|
type MatchPattern = string | RegExp | string[] | RegExp[]
|
||||||
|
|
||||||
|
@ -80,7 +82,7 @@ const KeepAliveImpl = {
|
||||||
const storageContainer = createElement('div')
|
const storageContainer = createElement('div')
|
||||||
|
|
||||||
sink.activate = (vnode, container, anchor) => {
|
sink.activate = (vnode, container, anchor) => {
|
||||||
move(vnode, container, anchor)
|
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
const component = vnode.component!
|
const component = vnode.component!
|
||||||
component.isDeactivated = false
|
component.isDeactivated = false
|
||||||
|
@ -91,7 +93,7 @@ const KeepAliveImpl = {
|
||||||
}
|
}
|
||||||
|
|
||||||
sink.deactivate = (vnode: VNode) => {
|
sink.deactivate = (vnode: VNode) => {
|
||||||
move(vnode, storageContainer, null)
|
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
|
||||||
queuePostRenderEffect(() => {
|
queuePostRenderEffect(() => {
|
||||||
const component = vnode.component!
|
const component = vnode.component!
|
||||||
if (component.da !== null) {
|
if (component.da !== null) {
|
||||||
|
@ -188,6 +190,10 @@ const KeepAliveImpl = {
|
||||||
vnode.el = cached.el
|
vnode.el = cached.el
|
||||||
vnode.anchor = cached.anchor
|
vnode.anchor = cached.anchor
|
||||||
vnode.component = cached.component
|
vnode.component = cached.component
|
||||||
|
if (vnode.transition) {
|
||||||
|
// recursively update transition hooks on subTree
|
||||||
|
setTransitionHooks(vnode, vnode.transition!)
|
||||||
|
}
|
||||||
// avoid vnode being mounted as fresh
|
// avoid vnode being mounted as fresh
|
||||||
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
|
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
|
||||||
// make this key the freshest
|
// make this key the freshest
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ShapeFlags } from '../shapeFlags'
|
||||||
import { isFunction, isArray } from '@vue/shared'
|
import { isFunction, isArray } from '@vue/shared'
|
||||||
import { ComponentInternalInstance, handleSetupResult } from '../component'
|
import { ComponentInternalInstance, handleSetupResult } from '../component'
|
||||||
import { Slots } from '../componentSlots'
|
import { Slots } from '../componentSlots'
|
||||||
import { RendererInternals } from '../renderer'
|
import { RendererInternals, MoveType } from '../renderer'
|
||||||
import { queuePostFlushCb, queueJob } from '../scheduler'
|
import { queuePostFlushCb, queueJob } from '../scheduler'
|
||||||
import { updateHOCHostEl } from '../componentRenderUtils'
|
import { updateHOCHostEl } from '../componentRenderUtils'
|
||||||
import { handleError, ErrorCodes } from '../errorHandling'
|
import { handleError, ErrorCodes } from '../errorHandling'
|
||||||
|
@ -213,7 +213,7 @@ export interface SuspenseBoundary<
|
||||||
effects: Function[]
|
effects: Function[]
|
||||||
resolve(): void
|
resolve(): void
|
||||||
recede(): void
|
recede(): void
|
||||||
move(container: HostElement, anchor: HostNode | null): void
|
move(container: HostElement, anchor: HostNode | null, type: MoveType): void
|
||||||
next(): HostNode | null
|
next(): HostNode | null
|
||||||
registerDep(
|
registerDep(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
|
@ -299,7 +299,7 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||||
unmount(fallbackTree as VNode, parentComponent, suspense, true)
|
unmount(fallbackTree as VNode, parentComponent, suspense, true)
|
||||||
}
|
}
|
||||||
// move content from off-dom container to actual container
|
// move content from off-dom container to actual container
|
||||||
move(subTree as VNode, container, anchor)
|
move(subTree as VNode, container, anchor, MoveType.ENTER)
|
||||||
const el = (vnode.el = (subTree as VNode).el!)
|
const el = (vnode.el = (subTree as VNode).el!)
|
||||||
// suspense as the root node of a component...
|
// suspense as the root node of a component...
|
||||||
if (parentComponent && parentComponent.subTree === vnode) {
|
if (parentComponent && parentComponent.subTree === vnode) {
|
||||||
|
@ -346,7 +346,7 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||||
|
|
||||||
// move content tree back to the off-dom container
|
// move content tree back to the off-dom container
|
||||||
const anchor = next(subTree)
|
const anchor = next(subTree)
|
||||||
move(subTree as VNode, hiddenContainer, null)
|
move(subTree as VNode, hiddenContainer, null, MoveType.LEAVE)
|
||||||
// remount the fallback tree
|
// remount the fallback tree
|
||||||
patch(
|
patch(
|
||||||
null,
|
null,
|
||||||
|
@ -372,11 +372,12 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
move(container, anchor) {
|
move(container, anchor, type) {
|
||||||
move(
|
move(
|
||||||
suspense.isResolved ? suspense.subTree : suspense.fallbackTree,
|
suspense.isResolved ? suspense.subTree : suspense.fallbackTree,
|
||||||
container,
|
container,
|
||||||
anchor
|
anchor,
|
||||||
|
type
|
||||||
)
|
)
|
||||||
suspense.container = container
|
suspense.container = container
|
||||||
},
|
},
|
||||||
|
|
|
@ -109,12 +109,20 @@ export interface RendererInternals<HostNode = any, HostElement = any> {
|
||||||
move: (
|
move: (
|
||||||
vnode: VNode<HostNode, HostElement>,
|
vnode: VNode<HostNode, HostElement>,
|
||||||
container: HostElement,
|
container: HostElement,
|
||||||
anchor: HostNode | null
|
anchor: HostNode | null,
|
||||||
|
type: MoveType,
|
||||||
|
parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null
|
||||||
) => void
|
) => void
|
||||||
next: (vnode: VNode<HostNode, HostElement>) => HostNode | null
|
next: (vnode: VNode<HostNode, HostElement>) => HostNode | null
|
||||||
options: RendererOptions<HostNode, HostElement>
|
options: RendererOptions<HostNode, HostElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum MoveType {
|
||||||
|
ENTER,
|
||||||
|
LEAVE,
|
||||||
|
REORDER
|
||||||
|
}
|
||||||
|
|
||||||
const prodEffectOptions = {
|
const prodEffectOptions = {
|
||||||
scheduler: queueJob
|
scheduler: queueJob
|
||||||
}
|
}
|
||||||
|
@ -367,9 +375,6 @@ export function createRenderer<
|
||||||
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
|
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (transition != null && !transition.persisted) {
|
|
||||||
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) {
|
||||||
|
@ -383,6 +388,9 @@ export function createRenderer<
|
||||||
optimized || vnode.dynamicChildren !== null
|
optimized || vnode.dynamicChildren !== null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (transition != null && !transition.persisted) {
|
||||||
|
transition.beforeEnter(el)
|
||||||
|
}
|
||||||
hostInsert(el, container, anchor)
|
hostInsert(el, container, anchor)
|
||||||
const vnodeMountedHook = props && props.onVnodeMounted
|
const vnodeMountedHook = props && props.onVnodeMounted
|
||||||
if (
|
if (
|
||||||
|
@ -747,7 +755,12 @@ export function createRenderer<
|
||||||
hostSetElementText(nextTarget, children as string)
|
hostSetElementText(nextTarget, children as string)
|
||||||
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||||
for (let i = 0; i < (children as HostVNode[]).length; i++) {
|
for (let i = 0; i < (children as HostVNode[]).length; i++) {
|
||||||
move((children as HostVNode[])[i], nextTarget, null)
|
move(
|
||||||
|
(children as HostVNode[])[i],
|
||||||
|
nextTarget,
|
||||||
|
null,
|
||||||
|
MoveType.REORDER
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
|
@ -1372,7 +1385,7 @@ export function createRenderer<
|
||||||
// There is no stable subsequence (e.g. a reverse)
|
// There is no stable subsequence (e.g. a reverse)
|
||||||
// OR current node is not among the stable sequence
|
// OR current node is not among the stable sequence
|
||||||
if (j < 0 || i !== increasingNewIndexSequence[j]) {
|
if (j < 0 || i !== increasingNewIndexSequence[j]) {
|
||||||
move(nextChild, container, anchor)
|
move(nextChild, container, anchor, MoveType.REORDER)
|
||||||
} else {
|
} else {
|
||||||
j--
|
j--
|
||||||
}
|
}
|
||||||
|
@ -1384,25 +1397,54 @@ export function createRenderer<
|
||||||
function move(
|
function move(
|
||||||
vnode: HostVNode,
|
vnode: HostVNode,
|
||||||
container: HostElement,
|
container: HostElement,
|
||||||
anchor: HostNode | null
|
anchor: HostNode | null,
|
||||||
|
type: MoveType,
|
||||||
|
parentSuspense: HostSuspenseBoundary | null = null
|
||||||
) {
|
) {
|
||||||
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
|
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
move(vnode.component!.subTree, container, anchor)
|
move(vnode.component!.subTree, container, anchor, type)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
vnode.suspense!.move(container, anchor)
|
vnode.suspense!.move(container, anchor, type)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (vnode.type === Fragment) {
|
if (vnode.type === Fragment) {
|
||||||
hostInsert(vnode.el!, container, anchor)
|
hostInsert(vnode.el!, container, anchor)
|
||||||
const children = vnode.children as HostVNode[]
|
const children = vnode.children as HostVNode[]
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
move(children[i], container, anchor)
|
move(children[i], container, anchor, type)
|
||||||
}
|
}
|
||||||
hostInsert(vnode.anchor!, container, anchor)
|
hostInsert(vnode.anchor!, container, anchor)
|
||||||
} else {
|
} else {
|
||||||
hostInsert(vnode.el!, container, anchor)
|
// Plain element
|
||||||
|
const { el, transition, shapeFlag } = vnode
|
||||||
|
const needTransition =
|
||||||
|
type !== MoveType.REORDER &&
|
||||||
|
shapeFlag & ShapeFlags.ELEMENT &&
|
||||||
|
transition != null
|
||||||
|
if (needTransition) {
|
||||||
|
if (type === MoveType.ENTER) {
|
||||||
|
transition!.beforeEnter(el!)
|
||||||
|
hostInsert(el!, container, anchor)
|
||||||
|
queuePostRenderEffect(() => transition!.enter(el!), parentSuspense)
|
||||||
|
} else {
|
||||||
|
const { leave, delayLeave, afterLeave } = transition!
|
||||||
|
const performLeave = () => {
|
||||||
|
leave(el!, () => {
|
||||||
|
hostInsert(el!, container, anchor)
|
||||||
|
afterLeave && afterLeave()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (delayLeave) {
|
||||||
|
delayLeave(performLeave)
|
||||||
|
} else {
|
||||||
|
performLeave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hostInsert(el!, container, anchor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue