fix(ssr): avoid updating subtree of async component if it is resolved (#12363)

close #12362
This commit is contained in:
edison 2024-11-15 22:11:21 +08:00 committed by GitHub
parent 1f75d4e6df
commit da7ad5e3d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 2 deletions

View File

@ -1324,6 +1324,84 @@ describe('SSR hydration', () => {
resolve({})
})
//#12362
test('nested async wrapper', async () => {
const Toggle = defineAsyncComponent(
() =>
new Promise(r => {
r(
defineComponent({
setup(_, { slots }) {
const show = ref(false)
onMounted(() => {
nextTick(() => {
show.value = true
})
})
return () =>
withDirectives(
h('div', null, [renderSlot(slots, 'default')]),
[[vShow, show.value]],
)
},
}) as any,
)
}),
)
const Wrapper = defineAsyncComponent(() => {
return new Promise(r => {
r(
defineComponent({
render(this: any) {
return renderSlot(this.$slots, 'default')
},
}) as any,
)
})
})
const count = ref(0)
const fn = vi.fn()
const Child = {
setup() {
onMounted(() => {
fn()
count.value++
})
return () => h('div', count.value)
},
}
const App = {
render() {
return h(Toggle, null, {
default: () =>
h(Wrapper, null, {
default: () =>
h(Wrapper, null, {
default: () => h(Child),
}),
}),
})
},
}
const root = document.createElement('div')
root.innerHTML = await renderToString(h(App))
expect(root.innerHTML).toMatchInlineSnapshot(
`"<div style="display:none;"><!--[--><!--[--><!--[--><div>0</div><!--]--><!--]--><!--]--></div>"`,
)
createSSRApp(App).mount(root)
await nextTick()
await nextTick()
expect(root.innerHTML).toMatchInlineSnapshot(
`"<div style=""><!--[--><!--[--><!--[--><div>1</div><!--]--><!--]--><!--]--></div>"`,
)
expect(fn).toBeCalledTimes(1)
})
test('unmount async wrapper before load (fragment)', async () => {
let resolve: any
const AsyncComp = defineAsyncComponent(

View File

@ -11,7 +11,7 @@ import {
normalizeVNode,
} from './vnode'
import { flushPostFlushCbs } from './scheduler'
import type { ComponentInternalInstance } from './component'
import type { ComponentInternalInstance, ComponentOptions } from './component'
import { invokeDirectiveHook } from './directives'
import { warn } from './warning'
import {
@ -308,7 +308,10 @@ export function createHydrationFunctions(
// 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)) {
if (
isAsyncWrapper(vnode) &&
!(vnode.type as ComponentOptions).__asyncResolved
) {
let subTree
if (isFragmentStart) {
subTree = createVNode(Fragment)