wip: suspense
This commit is contained in:
parent
cc748db33b
commit
1dc9d81e3e
@ -6,13 +6,13 @@ import {
|
|||||||
normalizeVNode,
|
normalizeVNode,
|
||||||
VNode,
|
VNode,
|
||||||
VNodeChildren,
|
VNodeChildren,
|
||||||
Suspense
|
Suspense,
|
||||||
|
createVNode
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setupStatefulComponent,
|
setupStatefulComponent
|
||||||
setCurrentInstance
|
|
||||||
} from './component'
|
} from './component'
|
||||||
import {
|
import {
|
||||||
renderComponentRoot,
|
renderComponentRoot,
|
||||||
@ -42,12 +42,7 @@ 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 {
|
import { SuspenseBoundary, createSuspenseBoundary } from './suspense'
|
||||||
SuspenseSymbol,
|
|
||||||
createSuspenseBoundary,
|
|
||||||
SuspenseBoundary
|
|
||||||
} from './suspense'
|
|
||||||
import { provide } from './apiInject'
|
|
||||||
|
|
||||||
const prodEffectOptions = {
|
const prodEffectOptions = {
|
||||||
scheduler: queueJob
|
scheduler: queueJob
|
||||||
@ -603,37 +598,70 @@ export function createRenderer<
|
|||||||
anchor: HostNode | null,
|
anchor: HostNode | null,
|
||||||
parentComponent: ComponentInternalInstance | null,
|
parentComponent: ComponentInternalInstance | null,
|
||||||
isSVG: boolean,
|
isSVG: boolean,
|
||||||
optimized: boolean
|
optimized: boolean,
|
||||||
|
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null = null
|
||||||
) {
|
) {
|
||||||
if (n1 == null) {
|
if (n1 == null) {
|
||||||
const parentSuspense =
|
const contentContainer = hostCreateElement('div')
|
||||||
parentComponent &&
|
const suspense = (n2.suspense = createSuspenseBoundary(
|
||||||
(parentComponent.provides[SuspenseSymbol as any] as SuspenseBoundary)
|
parentSuspense,
|
||||||
const suspense = (n2.suspense = createSuspenseBoundary(parentSuspense))
|
contentContainer
|
||||||
|
))
|
||||||
// provide this as the parent suspense for descendents
|
|
||||||
setCurrentInstance(parentComponent)
|
|
||||||
provide(SuspenseSymbol, suspense)
|
|
||||||
setCurrentInstance(null)
|
|
||||||
|
|
||||||
// start mounting the subtree off-dom
|
// start mounting the subtree off-dom
|
||||||
// - tracking async deps and buffering postQueue jobs on current boundary
|
// - TODO tracking async deps and buffering postQueue jobs on current boundary
|
||||||
|
const contentTree = (suspense.contentTree = childrenToFragment(n2))
|
||||||
|
processFragment(
|
||||||
|
null,
|
||||||
|
contentTree as VNode<HostNode, HostElement>,
|
||||||
|
contentContainer,
|
||||||
|
null,
|
||||||
|
parentComponent,
|
||||||
|
isSVG,
|
||||||
|
optimized
|
||||||
|
)
|
||||||
// now check if we have encountered any async deps
|
// now check if we have encountered any async deps
|
||||||
// yes: mount the fallback tree.
|
if (suspense.deps > 0) {
|
||||||
// Each time an async dep resolves, it pings the boundary
|
// yes: mount the fallback tree.
|
||||||
// and causes a re-entry.
|
// Each time an async dep resolves, it pings the boundary
|
||||||
|
// and causes a re-entry.
|
||||||
// no: just mount the tree
|
} else {
|
||||||
// - if have parent boundary that is still not resolved:
|
suspense.resolve()
|
||||||
// merge the buffered jobs into parent
|
}
|
||||||
// - else: flush buffered jobs.
|
|
||||||
// - mark resolved.
|
|
||||||
} else {
|
} else {
|
||||||
const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary
|
const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary<
|
||||||
|
HostNode,
|
||||||
|
HostElement
|
||||||
|
>
|
||||||
|
const oldContentTree = suspense.contentTree
|
||||||
|
const newContentTree = (suspense.contentTree = childrenToFragment(n2))
|
||||||
|
// patch suspense subTree as fragment
|
||||||
|
processFragment(
|
||||||
|
oldContentTree,
|
||||||
|
newContentTree,
|
||||||
|
container,
|
||||||
|
anchor,
|
||||||
|
parentComponent,
|
||||||
|
isSVG,
|
||||||
|
optimized
|
||||||
|
)
|
||||||
|
if (suspense.deps > 0) {
|
||||||
|
// still pending.
|
||||||
|
// patch the fallback tree.
|
||||||
|
} else {
|
||||||
|
suspense.resolve()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
@ -1,48 +1,48 @@
|
|||||||
import { warn } from './warning'
|
import { VNode } from './vnode'
|
||||||
|
import { queuePostFlushCb } from './scheduler'
|
||||||
|
|
||||||
export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
|
export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
|
||||||
|
|
||||||
export interface SuspenseBoundary {
|
export interface SuspenseBoundary<HostNode, HostElement> {
|
||||||
|
parent: SuspenseBoundary<HostNode, HostElement> | null
|
||||||
|
contentTree: VNode<HostNode, HostElement> | null
|
||||||
|
fallbackTree: VNode<HostNode, HostElement> | null
|
||||||
deps: number
|
deps: number
|
||||||
isResolved: boolean
|
isResolved: boolean
|
||||||
parent: SuspenseBoundary | null
|
bufferedJobs: Function[]
|
||||||
ping(): void
|
container: HostElement
|
||||||
resolve(): void
|
resolve(): void
|
||||||
onResolve(cb: () => void): void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSuspenseBoundary(
|
export function createSuspenseBoundary<HostNode, HostElement>(
|
||||||
parent: SuspenseBoundary | null
|
parent: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
): SuspenseBoundary {
|
container: HostElement
|
||||||
let onResolve: () => void
|
): SuspenseBoundary<HostNode, HostElement> {
|
||||||
|
const suspense: SuspenseBoundary<HostNode, HostElement> = {
|
||||||
if (parent && !parent.isResolved) {
|
parent,
|
||||||
parent.deps++
|
container,
|
||||||
}
|
|
||||||
|
|
||||||
const boundary: SuspenseBoundary = {
|
|
||||||
deps: 0,
|
deps: 0,
|
||||||
|
contentTree: null,
|
||||||
|
fallbackTree: null,
|
||||||
isResolved: false,
|
isResolved: false,
|
||||||
parent: parent && parent.isResolved ? parent : null,
|
bufferedJobs: [],
|
||||||
ping() {
|
|
||||||
// one of the deps resolved - re-entry from root suspense
|
|
||||||
if (boundary.parent) {
|
|
||||||
}
|
|
||||||
if (__DEV__ && boundary.deps < 0) {
|
|
||||||
warn(`Suspense boundary pinged when deps === 0. This is a bug.`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolve() {
|
resolve() {
|
||||||
boundary.isResolved = true
|
suspense.isResolved = true
|
||||||
if (parent && !parent.isResolved) {
|
let parent = suspense.parent
|
||||||
parent.ping()
|
let hasUnresolvedAncestor = false
|
||||||
} else {
|
while (parent) {
|
||||||
onResolve && onResolve()
|
if (!parent.isResolved) {
|
||||||
|
parent.bufferedJobs.push(...suspense.bufferedJobs)
|
||||||
|
hasUnresolvedAncestor = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
if (!hasUnresolvedAncestor) {
|
||||||
onResolve(cb: () => void) {
|
queuePostFlushCb(suspense.bufferedJobs)
|
||||||
onResolve = cb
|
}
|
||||||
|
suspense.isResolved = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return boundary
|
|
||||||
|
return suspense
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ export interface VNode<HostNode = any, HostElement = any> {
|
|||||||
ref: string | Function | null
|
ref: string | Function | null
|
||||||
children: NormalizedChildren<HostNode, HostElement>
|
children: NormalizedChildren<HostNode, HostElement>
|
||||||
component: ComponentInternalInstance | null
|
component: ComponentInternalInstance | null
|
||||||
suspense: SuspenseBoundary | null
|
suspense: SuspenseBoundary<HostNode, HostElement> | null
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
|
Loading…
Reference in New Issue
Block a user