wip: suspense ideas
This commit is contained in:
		
							parent
							
								
									a45d9567df
								
							
						
					
					
						commit
						8b3aa60a18
					
				| @ -5,12 +5,14 @@ import { | ||||
|   Portal, | ||||
|   normalizeVNode, | ||||
|   VNode, | ||||
|   VNodeChildren | ||||
|   VNodeChildren, | ||||
|   Suspense | ||||
| } from './vnode' | ||||
| import { | ||||
|   ComponentInternalInstance, | ||||
|   createComponentInstance, | ||||
|   setupStatefulComponent | ||||
|   setupStatefulComponent, | ||||
|   setCurrentInstance | ||||
| } from './component' | ||||
| import { | ||||
|   renderComponentRoot, | ||||
| @ -40,6 +42,12 @@ import { pushWarningContext, popWarningContext, warn } from './warning' | ||||
| import { invokeDirectiveHook } from './directives' | ||||
| import { ComponentPublicInstance } from './componentPublicInstanceProxy' | ||||
| import { App, createAppAPI } from './apiApp' | ||||
| import { | ||||
|   SuspenseSymbol, | ||||
|   createSuspenseBoundary, | ||||
|   SuspenseBoundary | ||||
| } from './suspense' | ||||
| import { provide } from './apiInject' | ||||
| 
 | ||||
| const prodEffectOptions = { | ||||
|   scheduler: queueJob | ||||
| @ -187,6 +195,17 @@ export function createRenderer< | ||||
|           optimized | ||||
|         ) | ||||
|         break | ||||
|       case Suspense: | ||||
|         processSuspense( | ||||
|           n1, | ||||
|           n2, | ||||
|           container, | ||||
|           anchor, | ||||
|           parentComponent, | ||||
|           isSVG, | ||||
|           optimized | ||||
|         ) | ||||
|         break | ||||
|       default: | ||||
|         if (shapeFlag & ShapeFlags.ELEMENT) { | ||||
|           processElement( | ||||
| @ -575,6 +594,44 @@ export function createRenderer< | ||||
|     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( | ||||
|     n1: HostVNode | null, | ||||
|     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 { isReactive } from '@vue/reactivity' | ||||
| import { AppContext } from './apiApp' | ||||
| import { SuspenseBoundary } from './suspense' | ||||
| 
 | ||||
| export const Fragment = __DEV__ ? Symbol('Fragment') : Symbol() | ||||
| export const Text = __DEV__ ? Symbol('Text') : Symbol() | ||||
| export const Empty = __DEV__ ? Symbol('Empty') : Symbol() | ||||
| export const Portal = __DEV__ ? Symbol('Portal') : Symbol() | ||||
| export const Suspense = __DEV__ ? Symbol('Suspense') : Symbol() | ||||
| 
 | ||||
| export type VNodeTypes = | ||||
|   | string | ||||
| @ -26,6 +28,7 @@ export type VNodeTypes = | ||||
|   | typeof Portal | ||||
|   | typeof Text | ||||
|   | typeof Empty | ||||
|   | typeof Suspense | ||||
| 
 | ||||
| type VNodeChildAtom<HostNode, HostElement> = | ||||
|   | VNode<HostNode, HostElement> | ||||
| @ -58,6 +61,7 @@ export interface VNode<HostNode = any, HostElement = any> { | ||||
|   ref: string | Function | null | ||||
|   children: NormalizedChildren<HostNode, HostElement> | ||||
|   component: ComponentInternalInstance | null | ||||
|   suspense: SuspenseBoundary | null | ||||
| 
 | ||||
|   // DOM
 | ||||
|   el: HostNode | null | ||||
| @ -168,6 +172,7 @@ export function createVNode( | ||||
|     ref: (props && props.ref) || null, | ||||
|     children: null, | ||||
|     component: null, | ||||
|     suspense: null, | ||||
|     el: null, | ||||
|     anchor: 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
 | ||||
|     // encountered an already-mounted vnode being used again.
 | ||||
|     component: null, | ||||
|     suspense: null, | ||||
|     el: null, | ||||
|     anchor: null | ||||
|   } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user