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…
Reference in New Issue
Block a user