wip: watcher callback handling inside suspense
This commit is contained in:
		
							parent
							
								
									51914c76e8
								
							
						
					
					
						commit
						356a01780b
					
				| @ -115,6 +115,8 @@ describe('renderer: suspense', () => { | ||||
| 
 | ||||
|   test.todo('buffer mounted/updated hooks & watch callbacks') | ||||
| 
 | ||||
|   test.todo('onResolve') | ||||
| 
 | ||||
|   test.todo('content update before suspense resolve') | ||||
| 
 | ||||
|   test.todo('unmount before suspense resolve') | ||||
|  | ||||
| @ -39,7 +39,11 @@ function injectHook( | ||||
|     warn( | ||||
|       `${apiName} is called when there is no active component instance to be ` + | ||||
|         `associated with. ` + | ||||
|         `Lifecycle injection APIs can only be used during execution of setup().` | ||||
|         `Lifecycle injection APIs can only be used during execution of setup().` + | ||||
|         (__FEATURE_SUSPENSE__ | ||||
|           ? ` If you are using async setup(), make sure to register lifecycle ` + | ||||
|             `hooks before the first await statement.` | ||||
|           : ``) | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -5,16 +5,21 @@ import { | ||||
|   Ref, | ||||
|   ReactiveEffectOptions | ||||
| } from '@vue/reactivity' | ||||
| import { queueJob, queuePostFlushCb } from './scheduler' | ||||
| import { queueJob } from './scheduler' | ||||
| import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared' | ||||
| import { recordEffect } from './apiReactivity' | ||||
| import { currentInstance, ComponentInternalInstance } from './component' | ||||
| import { | ||||
|   currentInstance, | ||||
|   ComponentInternalInstance, | ||||
|   currentSuspense | ||||
| } from './component' | ||||
| import { | ||||
|   ErrorCodes, | ||||
|   callWithErrorHandling, | ||||
|   callWithAsyncErrorHandling | ||||
| } from './errorHandling' | ||||
| import { onBeforeMount } from './apiLifecycle' | ||||
| import { onBeforeUnmount } from './apiLifecycle' | ||||
| import { queuePostRenderEffect } from './createRenderer' | ||||
| 
 | ||||
| export interface WatchOptions { | ||||
|   lazy?: boolean | ||||
| @ -38,14 +43,17 @@ type SimpleEffect = (onCleanup: CleanupRegistrator) => void | ||||
| 
 | ||||
| const invoke = (fn: Function) => fn() | ||||
| 
 | ||||
| // overload #1: simple effect
 | ||||
| export function watch(effect: SimpleEffect, options?: WatchOptions): StopHandle | ||||
| 
 | ||||
| // overload #2: single source + cb
 | ||||
| export function watch<T>( | ||||
|   source: WatcherSource<T>, | ||||
|   cb: (newValue: T, oldValue: T, onCleanup: CleanupRegistrator) => any, | ||||
|   options?: WatchOptions | ||||
| ): StopHandle | ||||
| 
 | ||||
| // overload #3: array of multiple sources + cb
 | ||||
| export function watch<T extends WatcherSource<unknown>[]>( | ||||
|   sources: T, | ||||
|   cb: ( | ||||
| @ -85,6 +93,7 @@ function doWatch( | ||||
|   { lazy, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ | ||||
| ): StopHandle { | ||||
|   const instance = currentInstance | ||||
|   const suspense = currentSuspense | ||||
| 
 | ||||
|   let getter: Function | ||||
|   if (isArray(source)) { | ||||
| @ -152,7 +161,7 @@ function doWatch( | ||||
|     flush === 'sync' | ||||
|       ? invoke | ||||
|       : flush === 'pre' | ||||
|         ? (job: () => void) => { | ||||
|         ? (job: () => any) => { | ||||
|             if (!instance || instance.vnode.el != null) { | ||||
|               queueJob(job) | ||||
|             } else { | ||||
| @ -161,7 +170,7 @@ function doWatch( | ||||
|               job() | ||||
|             } | ||||
|           } | ||||
|         : queuePostFlushCb | ||||
|         : (job: () => any) => queuePostRenderEffect(job, suspense) | ||||
| 
 | ||||
|   const runner = effect(getter, { | ||||
|     lazy: true, | ||||
| @ -198,7 +207,7 @@ export function instanceWatch( | ||||
|   const ctx = this.renderProxy as any | ||||
|   const getter = isString(source) ? () => ctx[source] : source.bind(ctx) | ||||
|   const stop = watch(getter, cb.bind(ctx), options) | ||||
|   onBeforeMount(stop, this) | ||||
|   onBeforeUnmount(stop, this) | ||||
|   return stop | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -23,6 +23,7 @@ import { | ||||
|   isArray, | ||||
|   isObject | ||||
| } from '@vue/shared' | ||||
| import { SuspenseBoundary } from './suspense' | ||||
| 
 | ||||
| export type Data = { [key: string]: unknown } | ||||
| 
 | ||||
| @ -206,6 +207,7 @@ export function createComponentInstance( | ||||
| } | ||||
| 
 | ||||
| export let currentInstance: ComponentInternalInstance | null = null | ||||
| export let currentSuspense: SuspenseBoundary | null = null | ||||
| 
 | ||||
| export const getCurrentInstance: () => ComponentInternalInstance | null = () => | ||||
|   currentInstance | ||||
| @ -216,7 +218,10 @@ export const setCurrentInstance = ( | ||||
|   currentInstance = instance | ||||
| } | ||||
| 
 | ||||
| export function setupStatefulComponent(instance: ComponentInternalInstance) { | ||||
| export function setupStatefulComponent( | ||||
|   instance: ComponentInternalInstance, | ||||
|   parentSuspense: SuspenseBoundary | null | ||||
| ) { | ||||
|   const Component = instance.type as ComponentOptions | ||||
|   // 1. create render proxy
 | ||||
|   instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers) as any | ||||
| @ -231,6 +236,7 @@ export function setupStatefulComponent(instance: ComponentInternalInstance) { | ||||
|       setup.length > 1 ? createSetupContext(instance) : null) | ||||
| 
 | ||||
|     currentInstance = instance | ||||
|     currentSuspense = parentSuspense | ||||
|     const setupResult = callWithErrorHandling( | ||||
|       setup, | ||||
|       instance, | ||||
| @ -238,6 +244,7 @@ export function setupStatefulComponent(instance: ComponentInternalInstance) { | ||||
|       [propsProxy, setupContext] | ||||
|     ) | ||||
|     currentInstance = null | ||||
|     currentSuspense = null | ||||
| 
 | ||||
|     if ( | ||||
|       setupResult && | ||||
| @ -256,16 +263,17 @@ export function setupStatefulComponent(instance: ComponentInternalInstance) { | ||||
|       } | ||||
|       return | ||||
|     } else { | ||||
|       handleSetupResult(instance, setupResult) | ||||
|       handleSetupResult(instance, setupResult, parentSuspense) | ||||
|     } | ||||
|   } else { | ||||
|     finishComponentSetup(instance) | ||||
|     finishComponentSetup(instance, parentSuspense) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function handleSetupResult( | ||||
|   instance: ComponentInternalInstance, | ||||
|   setupResult: unknown | ||||
|   setupResult: unknown, | ||||
|   parentSuspense: SuspenseBoundary | null | ||||
| ) { | ||||
|   if (isFunction(setupResult)) { | ||||
|     // setup returned an inline render function
 | ||||
| @ -281,10 +289,13 @@ export function handleSetupResult( | ||||
|       }` | ||||
|     ) | ||||
|   } | ||||
|   finishComponentSetup(instance) | ||||
|   finishComponentSetup(instance, parentSuspense) | ||||
| } | ||||
| 
 | ||||
| function finishComponentSetup(instance: ComponentInternalInstance) { | ||||
| function finishComponentSetup( | ||||
|   instance: ComponentInternalInstance, | ||||
|   parentSuspense: SuspenseBoundary | null | ||||
| ) { | ||||
|   const Component = instance.type as ComponentOptions | ||||
|   if (!instance.render) { | ||||
|     if (__DEV__ && !Component.render) { | ||||
| @ -299,8 +310,10 @@ function finishComponentSetup(instance: ComponentInternalInstance) { | ||||
|   // support for 2.x options
 | ||||
|   if (__FEATURE_OPTIONS__) { | ||||
|     currentInstance = instance | ||||
|     currentSuspense = parentSuspense | ||||
|     applyOptions(instance, Component) | ||||
|     currentInstance = null | ||||
|     currentSuspense = null | ||||
|   } | ||||
| 
 | ||||
|   if (instance.renderContext === EMPTY_OBJ) { | ||||
|  | ||||
| @ -78,7 +78,7 @@ function invokeHooks(hooks: Function[], arg?: any) { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function queuePostEffect( | ||||
| export function queuePostRenderEffect( | ||||
|   fn: Function | Function[], | ||||
|   suspense: SuspenseBoundary<any, any> | null | ||||
| ) { | ||||
| @ -357,7 +357,7 @@ export function createRenderer< | ||||
|     } | ||||
|     hostInsert(el, container, anchor) | ||||
|     if (props != null && props.vnodeMounted != null) { | ||||
|       queuePostEffect(() => { | ||||
|       queuePostRenderEffect(() => { | ||||
|         invokeDirectiveHook(props.vnodeMounted, parentComponent, vnode) | ||||
|       }, parentSuspense) | ||||
|     } | ||||
| @ -508,7 +508,7 @@ export function createRenderer< | ||||
|     } | ||||
| 
 | ||||
|     if (newProps.vnodeUpdated != null) { | ||||
|       queuePostEffect(() => { | ||||
|       queuePostRenderEffect(() => { | ||||
|         invokeDirectiveHook(newProps.vnodeUpdated, parentComponent, n2, n1) | ||||
|       }, parentSuspense) | ||||
|     } | ||||
| @ -700,7 +700,9 @@ export function createRenderer< | ||||
|       function resolveSuspense() { | ||||
|         const { subTree, fallbackTree, effects, vnode } = suspense | ||||
|         // unmount fallback tree
 | ||||
|         if (fallback.el) { | ||||
|           unmount(fallbackTree as HostVNode, parentComponent, suspense, true) | ||||
|         } | ||||
|         // move content from off-dom container to actual container
 | ||||
|         move(subTree as HostVNode, container, anchor) | ||||
|         const el = (vnode.el = (subTree as HostVNode).el as HostNode) | ||||
| @ -895,7 +897,7 @@ export function createRenderer< | ||||
| 
 | ||||
|     // setup stateful logic
 | ||||
|     if (initialVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { | ||||
|       setupStatefulComponent(instance) | ||||
|       setupStatefulComponent(instance, parentSuspense) | ||||
|     } | ||||
| 
 | ||||
|     // setup() is async. This component relies on async logic to be resolved
 | ||||
| @ -909,7 +911,7 @@ export function createRenderer< | ||||
|         parentSuspense.deps-- | ||||
|         // retry from this component
 | ||||
|         instance.asyncResolved = true | ||||
|         handleSetupResult(instance, asyncSetupResult) | ||||
|         handleSetupResult(instance, asyncSetupResult, parentSuspense) | ||||
|         setupRenderEffect( | ||||
|           instance, | ||||
|           parentSuspense, | ||||
| @ -965,7 +967,7 @@ export function createRenderer< | ||||
|         initialVNode.el = subTree.el | ||||
|         // mounted hook
 | ||||
|         if (instance.m !== null) { | ||||
|           queuePostEffect(instance.m, parentSuspense) | ||||
|           queuePostRenderEffect(instance.m, parentSuspense) | ||||
|         } | ||||
|         mounted = true | ||||
|       } else { | ||||
| @ -1018,7 +1020,7 @@ export function createRenderer< | ||||
|         } | ||||
|         // upated hook
 | ||||
|         if (instance.u !== null) { | ||||
|           queuePostEffect(instance.u, parentSuspense) | ||||
|           queuePostRenderEffect(instance.u, parentSuspense) | ||||
|         } | ||||
| 
 | ||||
|         if (__DEV__) { | ||||
| @ -1500,7 +1502,7 @@ export function createRenderer< | ||||
|     } | ||||
| 
 | ||||
|     if (props != null && props.vnodeUnmounted != null) { | ||||
|       queuePostEffect(() => { | ||||
|       queuePostRenderEffect(() => { | ||||
|         invokeDirectiveHook(props.vnodeUnmounted, parentComponent, vnode) | ||||
|       }, parentSuspense) | ||||
|     } | ||||
| @ -1525,9 +1527,9 @@ export function createRenderer< | ||||
|     unmount(subTree, instance, parentSuspense, doRemove) | ||||
|     // unmounted hook
 | ||||
|     if (um !== null) { | ||||
|       queuePostEffect(um, parentSuspense) | ||||
|       queuePostRenderEffect(um, parentSuspense) | ||||
|       // set unmounted after unmounted hooks are fired
 | ||||
|       queuePostEffect(() => { | ||||
|       queuePostRenderEffect(() => { | ||||
|         instance.isUnmounted = true | ||||
|       }, parentSuspense) | ||||
|     } | ||||
|  | ||||
| @ -5,8 +5,8 @@ import { isFunction } from '@vue/shared' | ||||
| export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol() | ||||
| 
 | ||||
| export interface SuspenseBoundary< | ||||
|   HostNode, | ||||
|   HostElement, | ||||
|   HostNode = any, | ||||
|   HostElement = any, | ||||
|   HostVNode = VNode<HostNode, HostElement> | ||||
| > { | ||||
|   vnode: HostVNode | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user