wip: suspense ideas
This commit is contained in:
parent
a45d9567df
commit
8b3aa60a18
@ -5,12 +5,14 @@ import {
|
|||||||
Portal,
|
Portal,
|
||||||
normalizeVNode,
|
normalizeVNode,
|
||||||
VNode,
|
VNode,
|
||||||
VNodeChildren
|
VNodeChildren,
|
||||||
|
Suspense
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setupStatefulComponent
|
setupStatefulComponent,
|
||||||
|
setCurrentInstance
|
||||||
} from './component'
|
} from './component'
|
||||||
import {
|
import {
|
||||||
renderComponentRoot,
|
renderComponentRoot,
|
||||||
@ -40,6 +42,12 @@ 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 {
|
||||||
|
SuspenseSymbol,
|
||||||
|
createSuspenseBoundary,
|
||||||
|
SuspenseBoundary
|
||||||
|
} from './suspense'
|
||||||
|
import { provide } from './apiInject'
|
||||||
|
|
||||||
const prodEffectOptions = {
|
const prodEffectOptions = {
|
||||||
scheduler: queueJob
|
scheduler: queueJob
|
||||||
@ -187,6 +195,17 @@ export function createRenderer<
|
|||||||
optimized
|
optimized
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
case Suspense:
|
||||||
|
processSuspense(
|
||||||
|
n1,
|
||||||
|
n2,
|
||||||
|
container,
|
||||||
|
anchor,
|
||||||
|
parentComponent,
|
||||||
|
isSVG,
|
||||||
|
optimized
|
||||||
|
)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
if (shapeFlag & ShapeFlags.ELEMENT) {
|
if (shapeFlag & ShapeFlags.ELEMENT) {
|
||||||
processElement(
|
processElement(
|
||||||
@ -575,6 +594,44 @@ export function createRenderer<
|
|||||||
processEmptyNode(n1, n2, container, anchor)
|
processEmptyNode(n1, n2, container, anchor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processSuspense(
|
||||||
|
n1: HostVNode | null,
|
||||||
|
n2: HostVNode,
|
||||||
|
container: HostElement,
|
||||||
|
anchor: HostNode | null,
|
||||||
|
parentComponent: ComponentInternalInstance | null,
|
||||||
|
isSVG: boolean,
|
||||||
|
optimized: boolean
|
||||||
|
) {
|
||||||
|
if (n1 == null) {
|
||||||
|
const parentSuspense =
|
||||||
|
parentComponent &&
|
||||||
|
(parentComponent.provides[SuspenseSymbol as any] as SuspenseBoundary)
|
||||||
|
const suspense = (n2.suspense = createSuspenseBoundary(parentSuspense))
|
||||||
|
|
||||||
|
// provide this as the parent suspense for descendents
|
||||||
|
setCurrentInstance(parentComponent)
|
||||||
|
provide(SuspenseSymbol, suspense)
|
||||||
|
setCurrentInstance(null)
|
||||||
|
|
||||||
|
// start mounting the subtree off-dom
|
||||||
|
// - tracking async deps and buffering postQueue jobs on current boundary
|
||||||
|
|
||||||
|
// now check if we have encountered any async deps
|
||||||
|
// yes: mount the fallback tree.
|
||||||
|
// Each time an async dep resolves, it pings the boundary
|
||||||
|
// and causes a re-entry.
|
||||||
|
|
||||||
|
// no: just mount the tree
|
||||||
|
// - if have parent boundary that is still not resolved:
|
||||||
|
// merge the buffered jobs into parent
|
||||||
|
// - else: flush buffered jobs.
|
||||||
|
// - mark resolved.
|
||||||
|
} else {
|
||||||
|
const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function processComponent(
|
function processComponent(
|
||||||
n1: HostVNode | null,
|
n1: HostVNode | null,
|
||||||
n2: HostVNode,
|
n2: HostVNode,
|
||||||
|
48
packages/runtime-core/src/suspense.ts
Normal file
48
packages/runtime-core/src/suspense.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
|
||||||
|
|
||||||
|
export interface SuspenseBoundary {
|
||||||
|
deps: number
|
||||||
|
isResolved: boolean
|
||||||
|
parent: SuspenseBoundary | null
|
||||||
|
ping(): void
|
||||||
|
resolve(): void
|
||||||
|
onResolve(cb: () => void): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSuspenseBoundary(
|
||||||
|
parent: SuspenseBoundary | null
|
||||||
|
): SuspenseBoundary {
|
||||||
|
let onResolve: () => void
|
||||||
|
|
||||||
|
if (parent && !parent.isResolved) {
|
||||||
|
parent.deps++
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundary: SuspenseBoundary = {
|
||||||
|
deps: 0,
|
||||||
|
isResolved: false,
|
||||||
|
parent: parent && parent.isResolved ? parent : null,
|
||||||
|
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() {
|
||||||
|
boundary.isResolved = true
|
||||||
|
if (parent && !parent.isResolved) {
|
||||||
|
parent.ping()
|
||||||
|
} else {
|
||||||
|
onResolve && onResolve()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onResolve(cb: () => void) {
|
||||||
|
onResolve = cb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boundary
|
||||||
|
}
|
@ -12,11 +12,13 @@ import { PatchFlags } from './patchFlags'
|
|||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { isReactive } from '@vue/reactivity'
|
import { isReactive } from '@vue/reactivity'
|
||||||
import { AppContext } from './apiApp'
|
import { AppContext } from './apiApp'
|
||||||
|
import { SuspenseBoundary } from './suspense'
|
||||||
|
|
||||||
export const Fragment = __DEV__ ? Symbol('Fragment') : Symbol()
|
export const Fragment = __DEV__ ? Symbol('Fragment') : Symbol()
|
||||||
export const Text = __DEV__ ? Symbol('Text') : Symbol()
|
export const Text = __DEV__ ? Symbol('Text') : Symbol()
|
||||||
export const Empty = __DEV__ ? Symbol('Empty') : Symbol()
|
export const Empty = __DEV__ ? Symbol('Empty') : Symbol()
|
||||||
export const Portal = __DEV__ ? Symbol('Portal') : Symbol()
|
export const Portal = __DEV__ ? Symbol('Portal') : Symbol()
|
||||||
|
export const Suspense = __DEV__ ? Symbol('Suspense') : Symbol()
|
||||||
|
|
||||||
export type VNodeTypes =
|
export type VNodeTypes =
|
||||||
| string
|
| string
|
||||||
@ -26,6 +28,7 @@ export type VNodeTypes =
|
|||||||
| typeof Portal
|
| typeof Portal
|
||||||
| typeof Text
|
| typeof Text
|
||||||
| typeof Empty
|
| typeof Empty
|
||||||
|
| typeof Suspense
|
||||||
|
|
||||||
type VNodeChildAtom<HostNode, HostElement> =
|
type VNodeChildAtom<HostNode, HostElement> =
|
||||||
| VNode<HostNode, HostElement>
|
| VNode<HostNode, HostElement>
|
||||||
@ -58,6 +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
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
@ -168,6 +172,7 @@ export function createVNode(
|
|||||||
ref: (props && props.ref) || null,
|
ref: (props && props.ref) || null,
|
||||||
children: null,
|
children: null,
|
||||||
component: null,
|
component: null,
|
||||||
|
suspense: null,
|
||||||
el: null,
|
el: null,
|
||||||
anchor: null,
|
anchor: null,
|
||||||
target: null,
|
target: null,
|
||||||
@ -221,6 +226,7 @@ export function cloneVNode(vnode: VNode): VNode {
|
|||||||
// mounted VNodes. If they are somehow not null, this means we have
|
// mounted VNodes. If they are somehow not null, this means we have
|
||||||
// encountered an already-mounted vnode being used again.
|
// encountered an already-mounted vnode being used again.
|
||||||
component: null,
|
component: null,
|
||||||
|
suspense: null,
|
||||||
el: null,
|
el: null,
|
||||||
anchor: null
|
anchor: null
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user