fix(ssr): handle hydrated async component unmounted before resolve
fix #3787
This commit is contained in:
		
							parent
							
								
									b57e995edd
								
							
						
					
					
						commit
						b46a4dccf6
					
				| @ -626,7 +626,7 @@ describe('SSR hydration', () => { | ||||
|     expect(spy).toHaveBeenCalled() | ||||
|   }) | ||||
| 
 | ||||
|   test('execute the updateComponent(AsyncComponentWrapper) before the async component is resolved', async () => { | ||||
|   test('update async wrapper before resolve', async () => { | ||||
|     const Comp = { | ||||
|       render() { | ||||
|         return h('h1', 'Async component') | ||||
| @ -687,6 +687,57 @@ describe('SSR hydration', () => { | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   // #3787
 | ||||
|   test('unmount async wrapper before load', async () => { | ||||
|     let resolve: any | ||||
|     const AsyncComp = defineAsyncComponent( | ||||
|       () => | ||||
|         new Promise(r => { | ||||
|           resolve = r | ||||
|         }) | ||||
|     ) | ||||
| 
 | ||||
|     const show = ref(true) | ||||
|     const root = document.createElement('div') | ||||
|     root.innerHTML = '<div><div>async</div></div>' | ||||
| 
 | ||||
|     createSSRApp({ | ||||
|       render() { | ||||
|         return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')]) | ||||
|       } | ||||
|     }).mount(root) | ||||
| 
 | ||||
|     show.value = false | ||||
|     await nextTick() | ||||
|     expect(root.innerHTML).toBe('<div><div>hi</div></div>') | ||||
|     resolve({}) | ||||
|   }) | ||||
| 
 | ||||
|   test('unmount async wrapper before load (fragment)', async () => { | ||||
|     let resolve: any | ||||
|     const AsyncComp = defineAsyncComponent( | ||||
|       () => | ||||
|         new Promise(r => { | ||||
|           resolve = r | ||||
|         }) | ||||
|     ) | ||||
| 
 | ||||
|     const show = ref(true) | ||||
|     const root = document.createElement('div') | ||||
|     root.innerHTML = '<div><!--[-->async<!--]--></div>' | ||||
| 
 | ||||
|     createSSRApp({ | ||||
|       render() { | ||||
|         return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')]) | ||||
|       } | ||||
|     }).mount(root) | ||||
| 
 | ||||
|     show.value = false | ||||
|     await nextTick() | ||||
|     expect(root.innerHTML).toBe('<div><div>hi</div></div>') | ||||
|     resolve({}) | ||||
|   }) | ||||
| 
 | ||||
|   test('elements with camel-case in svg ', () => { | ||||
|     const { vnode, container } = mountWithHydration( | ||||
|       '<animateTransform></animateTransform>', | ||||
|  | ||||
| @ -5,7 +5,9 @@ import { | ||||
|   Comment, | ||||
|   Static, | ||||
|   Fragment, | ||||
|   VNodeHook | ||||
|   VNodeHook, | ||||
|   createVNode, | ||||
|   createTextVNode | ||||
| } from './vnode' | ||||
| import { flushPostFlushCbs } from './scheduler' | ||||
| import { ComponentInternalInstance } from './component' | ||||
| @ -19,6 +21,7 @@ import { | ||||
|   queueEffectWithSuspense | ||||
| } from './components/Suspense' | ||||
| import { TeleportImpl, TeleportVNode } from './components/Teleport' | ||||
| import { isAsyncWrapper } from './apiAsyncComponent' | ||||
| 
 | ||||
| export type RootHydrateFunction = ( | ||||
|   vnode: VNode<Node, Element>, | ||||
| @ -187,12 +190,32 @@ export function createHydrationFunctions( | ||||
|             isSVGContainer(container), | ||||
|             optimized | ||||
|           ) | ||||
| 
 | ||||
|           // component may be async, so in the case of fragments we cannot rely
 | ||||
|           // on component's rendered output to determine the end of the fragment
 | ||||
|           // instead, we do a lookahead to find the end anchor node.
 | ||||
|           nextNode = isFragmentStart | ||||
|             ? locateClosingAsyncAnchor(node) | ||||
|             : nextSibling(node) | ||||
| 
 | ||||
|           // #3787
 | ||||
|           // if component is async, it may get moved / unmounted before its
 | ||||
|           // inner component is loaded, so we need to give it a placeholder
 | ||||
|           // vnode that matches its adopted DOM.
 | ||||
|           if (isAsyncWrapper(vnode)) { | ||||
|             let subTree | ||||
|             if (isFragmentStart) { | ||||
|               subTree = createVNode(Fragment) | ||||
|               subTree.anchor = nextNode | ||||
|                 ? nextNode.previousSibling | ||||
|                 : container.lastChild | ||||
|             } else { | ||||
|               subTree = | ||||
|                 node.nodeType === 3 ? createTextVNode('') : createVNode('div') | ||||
|             } | ||||
|             subTree.el = node | ||||
|             vnode.component!.subTree = subTree | ||||
|           } | ||||
|         } else if (shapeFlag & ShapeFlags.TELEPORT) { | ||||
|           if (domType !== DOMNodeTypes.COMMENT) { | ||||
|             nextNode = onMismatch() | ||||
|  | ||||
| @ -1462,7 +1462,7 @@ function baseCreateRenderer( | ||||
|               // which means it won't track dependencies - but it's ok because
 | ||||
|               // a server-rendered async wrapper is already in resolved state
 | ||||
|               // and it will never need to change.
 | ||||
|               hydrateSubTree | ||||
|               () => !instance.isUnmounted && hydrateSubTree() | ||||
|             ) | ||||
|           } else { | ||||
|             hydrateSubTree() | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user