wip: suspense refactor

This commit is contained in:
Evan You 2019-09-10 11:01:11 -04:00
parent c9e625864a
commit a16c87be63
3 changed files with 86 additions and 56 deletions

View File

@ -23,7 +23,6 @@ describe('renderer: suspense', () => {
}
})
// TODO test mounted hook & watch callback buffering
const AsyncChild = createAsyncComponent(
() =>
new Promise(resolve => {
@ -62,7 +61,6 @@ describe('renderer: suspense', () => {
const Comp = {
name: 'root',
setup() {
// TODO test fallback
return () =>
h(Suspense, [msg.value, h(Mid), h(AsyncChild2, { msg: 'child 2' })])
}
@ -79,7 +77,9 @@ describe('renderer: suspense', () => {
)
})
test.todo('fallback content update')
test.todo('buffer mounted/updated hooks & watch callbacks')
test.todo('fallback content')
test.todo('content update before suspense resolve')

View File

@ -44,7 +44,11 @@ import { pushWarningContext, popWarningContext, warn } from './warning'
import { invokeDirectiveHook } from './directives'
import { ComponentPublicInstance } from './componentPublicInstanceProxy'
import { App, createAppAPI } from './apiApp'
import { SuspenseBoundary, createSuspenseBoundary } from './suspense'
import {
SuspenseBoundary,
createSuspenseBoundary,
normalizeSuspenseChildren
} from './suspense'
import { provide } from './apiInject'
const prodEffectOptions = {
@ -609,26 +613,6 @@ export function createRenderer<
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null = null
) {
if (n1 == null) {
const contentContainer = hostCreateElement('div')
function retry() {
processFragment(
suspense.oldSubTree,
suspense.subTree as HostVNode,
contentContainer,
null,
parentComponent,
isSVG,
optimized
)
if (suspense.deps > 0) {
// still pending.
// TODO patch the fallback tree.
} else {
suspense.resolve()
}
}
function resolve() {
// unmount fallback tree
unmount(suspense.fallbackTree as HostVNode, parentComponent, true)
@ -657,7 +641,7 @@ export function createRenderer<
const suspense = (n2.suspense = createSuspenseBoundary(
n2,
parentSuspense,
retry,
hostCreateElement('div'),
resolve
))
@ -668,15 +652,16 @@ export function createRenderer<
setCurrentInstance(null)
}
// start mounting the subtree off-dom
const { content, fallback } = normalizeSuspenseChildren(n2)
suspense.subTree = content
suspense.fallbackTree = fallback
// start mounting the content subtree in an off-dom container
// TODO should buffer postQueue jobs on current boundary
const subTree = (suspense.subTree = suspense.oldSubTree = childrenToFragment(
n2
))
processFragment(
patch(
null,
subTree as HostVNode,
contentContainer,
content,
suspense.container,
null,
parentComponent,
isSVG,
@ -684,14 +669,19 @@ export function createRenderer<
)
// now check if we have encountered any async deps
if (suspense.deps > 0) {
// TODO mount the fallback tree.
processEmptyNode(
// mount the fallback tree
patch(
null,
(suspense.fallbackTree = createVNode(Empty)),
fallback,
container,
anchor
anchor,
parentComponent,
isSVG,
optimized
)
n2.el = fallback.el
} else {
// Suspense has no async deps. Just resolve.
suspense.resolve()
}
} else {
@ -700,34 +690,52 @@ export function createRenderer<
HostElement
>
suspense.vnode = n2
const { content, fallback } = normalizeSuspenseChildren(n2)
const oldSubTree = (suspense.oldSubTree = suspense.subTree)
const newContentTree = (suspense.subTree = childrenToFragment(n2))
suspense.subTree = content
const oldFallbackTree = (suspense.oldFallbackTree = suspense.fallbackTree)
suspense.fallbackTree = fallback
if (!suspense.isResolved) {
suspense.retry()
} else {
// just normal patch inner content as a fragment
processFragment(
patch(
oldSubTree,
newContentTree,
container,
content,
suspense.container,
null,
parentComponent,
isSVG,
optimized
)
n2.el = newContentTree.el
if (suspense.deps > 0) {
// still pending. patch the fallback tree.
patch(
oldFallbackTree,
fallback,
container,
anchor,
parentComponent,
isSVG,
optimized
)
n2.el = fallback.el
} else {
suspense.resolve()
}
} else {
// just normal patch inner content as a fragment
patch(
oldSubTree,
content,
container,
anchor,
parentComponent,
isSVG,
optimized
)
n2.el = content.el
}
}
}
function childrenToFragment(vnode: HostVNode): HostVNode {
return vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN
? createVNode(Fragment, null, vnode.children)
: vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN
? createVNode(Fragment, null, [vnode.children])
: createVNode(Fragment, null, [])
}
function processComponent(
n1: HostVNode | null,
n2: HostVNode,

View File

@ -1,4 +1,5 @@
import { VNode } from './vnode'
import { VNode, normalizeVNode } from './vnode'
import { ShapeFlags } from '.'
export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
@ -9,6 +10,7 @@ export interface SuspenseBoundary<
> {
vnode: HostVNode
parent: SuspenseBoundary<HostNode, HostElement> | null
container: HostElement
subTree: HostVNode | null
oldSubTree: HostVNode | null
fallbackTree: HostVNode | null
@ -16,19 +18,19 @@ export interface SuspenseBoundary<
deps: number
isResolved: boolean
bufferedJobs: Function[]
retry(): void
resolve(): void
}
export function createSuspenseBoundary<HostNode, HostElement>(
vnode: VNode<HostNode, HostElement>,
parent: SuspenseBoundary<HostNode, HostElement> | null,
retry: () => void,
container: HostElement,
resolve: () => void
): SuspenseBoundary<HostNode, HostElement> {
return {
vnode,
parent,
container,
deps: 0,
subTree: null,
oldSubTree: null,
@ -36,7 +38,27 @@ export function createSuspenseBoundary<HostNode, HostElement>(
oldFallbackTree: null,
isResolved: false,
bufferedJobs: [],
retry,
resolve
}
}
export function normalizeSuspenseChildren(
vnode: VNode
): {
content: VNode
fallback: VNode
} {
const { shapeFlag } = vnode
const children = vnode.children as any
if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
return {
content: normalizeVNode(children.default()),
fallback: normalizeVNode(children.fallback ? children.fallback() : null)
}
} else {
return {
content: normalizeVNode(children),
fallback: normalizeVNode(null)
}
}
}