refactor: move resolveSuspense out

This commit is contained in:
Evan You 2019-09-11 17:38:26 -04:00
parent 6dc91971d1
commit bbc3442c52
3 changed files with 80 additions and 62 deletions

View File

@ -286,7 +286,7 @@ describe('renderer: suspense', () => {
expect(calls).toEqual([]) expect(calls).toEqual([])
}) })
test.todo('unmount suspense after resolve') test('unmount suspense after resolve', () => {})
test.todo('unmount suspense before resolve') test.todo('unmount suspense before resolve')

View File

@ -721,63 +721,16 @@ export function createRenderer<
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean
) { ) {
const hiddenContainer = hostCreateElement('div')
const suspense = (n2.suspense = createSuspenseBoundary( const suspense = (n2.suspense = createSuspenseBoundary(
n2, n2,
parentSuspense, parentSuspense,
hostCreateElement('div'), parentComponent,
resolveSuspense container,
hiddenContainer,
anchor
)) ))
function resolveSuspense() {
if (__DEV__) {
if (suspense.isResolved) {
throw new Error(
`suspense.resolve() is called when it's already resolved`
)
}
if (suspense.isUnmounted) {
throw new Error(
`suspense.resolve() is called when it's already unmounted`
)
}
}
const { subTree, fallbackTree, effects, vnode } = suspense
// unmount fallback tree
if (fallbackTree.el) {
unmount(fallbackTree as HostVNode, parentComponent, suspense, true)
}
// move content from off-dom container to actual container
move(subTree as HostVNode, container, anchor)
const el = (vnode.el = (subTree as HostVNode).el as HostNode)
// suspense as the root node of a component...
if (parentComponent && parentComponent.subTree === vnode) {
parentComponent.vnode.el = el
updateHOCHostEl(parentComponent, el)
}
// check if there is a pending parent suspense
let parent = suspense.parent
let hasUnresolvedAncestor = false
while (parent) {
if (!parent.isResolved) {
// found a pending parent suspense, merge buffered post jobs
// into that parent
parent.effects.push(...effects)
hasUnresolvedAncestor = true
break
}
}
// no pending parent suspense, flush all jobs
if (!hasUnresolvedAncestor) {
queuePostFlushCb(effects)
}
suspense.isResolved = true
// invoke @resolve event
const onResolve = vnode.props && vnode.props.onResolve
if (isFunction(onResolve)) {
onResolve()
}
}
const { content, fallback } = normalizeSuspenseChildren(n2) const { content, fallback } = normalizeSuspenseChildren(n2)
suspense.subTree = content suspense.subTree = content
suspense.fallbackTree = fallback suspense.fallbackTree = fallback
@ -786,7 +739,7 @@ export function createRenderer<
patch( patch(
null, null,
content, content,
suspense.container, hiddenContainer,
null, null,
parentComponent, parentComponent,
suspense, suspense,
@ -809,7 +762,7 @@ export function createRenderer<
n2.el = fallback.el n2.el = fallback.el
} else { } else {
// Suspense has no async deps. Just resolve. // Suspense has no async deps. Just resolve.
suspense.resolve() resolveSuspense(suspense)
} }
} }
@ -831,7 +784,7 @@ export function createRenderer<
patch( patch(
oldSubTree, oldSubTree,
content, content,
suspense.container, suspense.hiddenContainer,
null, null,
parentComponent, parentComponent,
suspense, suspense,
@ -873,6 +826,64 @@ export function createRenderer<
suspense.fallbackTree = fallback suspense.fallbackTree = fallback
} }
function resolveSuspense(suspense: HostSuspsenseBoundary) {
if (__DEV__) {
if (suspense.isResolved) {
throw new Error(
`suspense.resolve() is called when it's already resolved`
)
}
if (suspense.isUnmounted) {
throw new Error(
`suspense.resolve() is called when it's already unmounted`
)
}
}
const {
subTree,
fallbackTree,
effects,
vnode,
parentComponent,
container,
anchor
} = suspense
// unmount fallback tree
if (fallbackTree.el) {
unmount(fallbackTree as HostVNode, parentComponent, suspense, true)
}
// move content from off-dom container to actual container
move(subTree as HostVNode, container, anchor)
const el = (vnode.el = (subTree as HostVNode).el as HostNode)
// suspense as the root node of a component...
if (parentComponent && parentComponent.subTree === vnode) {
parentComponent.vnode.el = el
updateHOCHostEl(parentComponent, el)
}
// check if there is a pending parent suspense
let parent = suspense.parent
let hasUnresolvedAncestor = false
while (parent) {
if (!parent.isResolved) {
// found a pending parent suspense, merge buffered post jobs
// into that parent
parent.effects.push(...effects)
hasUnresolvedAncestor = true
break
}
}
// no pending parent suspense, flush all jobs
if (!hasUnresolvedAncestor) {
queuePostFlushCb(effects)
}
suspense.isResolved = true
// invoke @resolve event
const onResolve = vnode.props && vnode.props.onResolve
if (isFunction(onResolve)) {
onResolve()
}
}
function processComponent( function processComponent(
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,
@ -992,7 +1003,7 @@ export function createRenderer<
) )
updateHOCHostEl(instance, initialVNode.el as HostNode) updateHOCHostEl(instance, initialVNode.el as HostNode)
if (parentSuspense.deps === 0) { if (parentSuspense.deps === 0) {
parentSuspense.resolve() resolveSuspense(parentSuspense)
} }
}) })
// give it a placeholder // give it a placeholder
@ -1621,7 +1632,7 @@ export function createRenderer<
) { ) {
parentSuspense.deps-- parentSuspense.deps--
if (parentSuspense.deps === 0) { if (parentSuspense.deps === 0) {
parentSuspense.resolve() resolveSuspense(parentSuspense)
} }
} }
} }

View File

@ -1,6 +1,7 @@
import { VNode, normalizeVNode } from './vnode' import { VNode, normalizeVNode } from './vnode'
import { ShapeFlags } from '.' import { ShapeFlags } from '.'
import { isFunction } from '@vue/shared' import { isFunction } from '@vue/shared'
import { ComponentInternalInstance } from './component'
export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol() export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
@ -11,33 +12,39 @@ export interface SuspenseBoundary<
> { > {
vnode: HostVNode vnode: HostVNode
parent: SuspenseBoundary<HostNode, HostElement> | null parent: SuspenseBoundary<HostNode, HostElement> | null
parentComponent: ComponentInternalInstance | null
container: HostElement container: HostElement
hiddenContainer: HostElement
anchor: HostNode | null
subTree: HostVNode subTree: HostVNode
fallbackTree: HostVNode fallbackTree: HostVNode
deps: number deps: number
isResolved: boolean isResolved: boolean
isUnmounted: boolean isUnmounted: boolean
effects: Function[] effects: Function[]
resolve(): void
} }
export function createSuspenseBoundary<HostNode, HostElement>( export function createSuspenseBoundary<HostNode, HostElement>(
vnode: VNode<HostNode, HostElement>, vnode: VNode<HostNode, HostElement>,
parent: SuspenseBoundary<HostNode, HostElement> | null, parent: SuspenseBoundary<HostNode, HostElement> | null,
parentComponent: ComponentInternalInstance | null,
container: HostElement, container: HostElement,
resolve: () => void hiddenContainer: HostElement,
anchor: HostNode | null
): SuspenseBoundary<HostNode, HostElement> { ): SuspenseBoundary<HostNode, HostElement> {
return { return {
vnode, vnode,
parent, parent,
parentComponent,
container, container,
hiddenContainer,
anchor,
deps: 0, deps: 0,
subTree: null as any, // will be set immediately after creation subTree: null as any, // will be set immediately after creation
fallbackTree: null as any, // will be set immediately after creation fallbackTree: null as any, // will be set immediately after creation
isResolved: false, isResolved: false,
isUnmounted: false, isUnmounted: false,
effects: [], effects: []
resolve
} }
} }