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( const AsyncChild = createAsyncComponent(
() => () =>
new Promise(resolve => { new Promise(resolve => {
@ -62,7 +61,6 @@ describe('renderer: suspense', () => {
const Comp = { const Comp = {
name: 'root', name: 'root',
setup() { setup() {
// TODO test fallback
return () => return () =>
h(Suspense, [msg.value, h(Mid), h(AsyncChild2, { msg: 'child 2' })]) 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') test.todo('content update before suspense resolve')

View File

@ -44,7 +44,11 @@ import { pushWarningContext, popWarningContext, warn } from './warning'
import { invokeDirectiveHook } from './directives' import { invokeDirectiveHook } from './directives'
import { ComponentPublicInstance } from './componentPublicInstanceProxy' import { ComponentPublicInstance } from './componentPublicInstanceProxy'
import { App, createAppAPI } from './apiApp' import { App, createAppAPI } from './apiApp'
import { SuspenseBoundary, createSuspenseBoundary } from './suspense' import {
SuspenseBoundary,
createSuspenseBoundary,
normalizeSuspenseChildren
} from './suspense'
import { provide } from './apiInject' import { provide } from './apiInject'
const prodEffectOptions = { const prodEffectOptions = {
@ -609,26 +613,6 @@ export function createRenderer<
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null = null parentSuspense: SuspenseBoundary<HostNode, HostElement> | null = null
) { ) {
if (n1 == 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() { function resolve() {
// unmount fallback tree // unmount fallback tree
unmount(suspense.fallbackTree as HostVNode, parentComponent, true) unmount(suspense.fallbackTree as HostVNode, parentComponent, true)
@ -657,7 +641,7 @@ export function createRenderer<
const suspense = (n2.suspense = createSuspenseBoundary( const suspense = (n2.suspense = createSuspenseBoundary(
n2, n2,
parentSuspense, parentSuspense,
retry, hostCreateElement('div'),
resolve resolve
)) ))
@ -668,15 +652,16 @@ export function createRenderer<
setCurrentInstance(null) 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 // TODO should buffer postQueue jobs on current boundary
const subTree = (suspense.subTree = suspense.oldSubTree = childrenToFragment( patch(
n2
))
processFragment(
null, null,
subTree as HostVNode, content,
contentContainer, suspense.container,
null, null,
parentComponent, parentComponent,
isSVG, isSVG,
@ -684,14 +669,19 @@ export function createRenderer<
) )
// now check if we have encountered any async deps // now check if we have encountered any async deps
if (suspense.deps > 0) { if (suspense.deps > 0) {
// TODO mount the fallback tree. // mount the fallback tree
processEmptyNode( patch(
null, null,
(suspense.fallbackTree = createVNode(Empty)), fallback,
container, container,
anchor anchor,
parentComponent,
isSVG,
optimized
) )
n2.el = fallback.el
} else { } else {
// Suspense has no async deps. Just resolve.
suspense.resolve() suspense.resolve()
} }
} else { } else {
@ -700,34 +690,52 @@ export function createRenderer<
HostElement HostElement
> >
suspense.vnode = n2 suspense.vnode = n2
const { content, fallback } = normalizeSuspenseChildren(n2)
const oldSubTree = (suspense.oldSubTree = suspense.subTree) 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) { if (!suspense.isResolved) {
suspense.retry() patch(
} else {
// just normal patch inner content as a fragment
processFragment(
oldSubTree, oldSubTree,
newContentTree, content,
container, suspense.container,
null, null,
parentComponent, parentComponent,
isSVG, isSVG,
optimized 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( function processComponent(
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, 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() export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
@ -9,6 +10,7 @@ export interface SuspenseBoundary<
> { > {
vnode: HostVNode vnode: HostVNode
parent: SuspenseBoundary<HostNode, HostElement> | null parent: SuspenseBoundary<HostNode, HostElement> | null
container: HostElement
subTree: HostVNode | null subTree: HostVNode | null
oldSubTree: HostVNode | null oldSubTree: HostVNode | null
fallbackTree: HostVNode | null fallbackTree: HostVNode | null
@ -16,19 +18,19 @@ export interface SuspenseBoundary<
deps: number deps: number
isResolved: boolean isResolved: boolean
bufferedJobs: Function[] bufferedJobs: Function[]
retry(): void
resolve(): void 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,
retry: () => void, container: HostElement,
resolve: () => void resolve: () => void
): SuspenseBoundary<HostNode, HostElement> { ): SuspenseBoundary<HostNode, HostElement> {
return { return {
vnode, vnode,
parent, parent,
container,
deps: 0, deps: 0,
subTree: null, subTree: null,
oldSubTree: null, oldSubTree: null,
@ -36,7 +38,27 @@ export function createSuspenseBoundary<HostNode, HostElement>(
oldFallbackTree: null, oldFallbackTree: null,
isResolved: false, isResolved: false,
bufferedJobs: [], bufferedJobs: [],
retry,
resolve 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)
}
}
}