mirror of https://github.com/vuejs/core.git
fix(hydration): prevent lazy hydration for updated components (#13511)
close #13510
This commit is contained in:
parent
00695a5b41
commit
a9269c642b
|
@ -1160,6 +1160,69 @@ describe('SSR hydration', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #13510
|
||||||
|
test('update async component after parent mount before async component resolve', async () => {
|
||||||
|
const Comp = {
|
||||||
|
props: ['toggle'],
|
||||||
|
render(this: any) {
|
||||||
|
return h('h1', [
|
||||||
|
this.toggle ? 'Async component' : 'Updated async component',
|
||||||
|
])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let serverResolve: any
|
||||||
|
let AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
serverResolve = r
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const toggle = ref(true)
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
// change state, after mount and before async component resolve
|
||||||
|
nextTick(() => (toggle.value = false))
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return h(AsyncComp, { toggle: toggle.value })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// server render
|
||||||
|
const htmlPromise = renderToString(h(App))
|
||||||
|
serverResolve(Comp)
|
||||||
|
const html = await htmlPromise
|
||||||
|
expect(html).toMatchInlineSnapshot(`"<h1>Async component</h1>"`)
|
||||||
|
|
||||||
|
// hydration
|
||||||
|
let clientResolve: any
|
||||||
|
AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
clientResolve = r
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const container = document.createElement('div')
|
||||||
|
container.innerHTML = html
|
||||||
|
createSSRApp(App).mount(container)
|
||||||
|
|
||||||
|
// resolve
|
||||||
|
clientResolve(Comp)
|
||||||
|
await new Promise(r => setTimeout(r))
|
||||||
|
|
||||||
|
// prevent lazy hydration since the component has been patched
|
||||||
|
expect('Skipping lazy hydration for component').toHaveBeenWarned()
|
||||||
|
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
|
||||||
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<h1>Updated async component</h1>"`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('hydrate safely when property used by async setup changed before render', async () => {
|
test('hydrate safely when property used by async setup changed before render', async () => {
|
||||||
const toggle = ref(true)
|
const toggle = ref(true)
|
||||||
|
|
||||||
|
|
|
@ -123,28 +123,30 @@ export function defineAsyncComponent<
|
||||||
|
|
||||||
__asyncHydrate(el, instance, hydrate) {
|
__asyncHydrate(el, instance, hydrate) {
|
||||||
let patched = false
|
let patched = false
|
||||||
|
;(instance.bu || (instance.bu = [])).push(() => (patched = true))
|
||||||
|
const performHydrate = () => {
|
||||||
|
// skip hydration if the component has been patched
|
||||||
|
if (patched) {
|
||||||
|
if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`Skipping lazy hydration for component '${getComponentName(resolvedComp!) || resolvedComp!.__file}': ` +
|
||||||
|
`it was updated before lazy hydration performed.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hydrate()
|
||||||
|
}
|
||||||
const doHydrate = hydrateStrategy
|
const doHydrate = hydrateStrategy
|
||||||
? () => {
|
? () => {
|
||||||
const performHydrate = () => {
|
|
||||||
// skip hydration if the component has been patched
|
|
||||||
if (__DEV__ && patched) {
|
|
||||||
warn(
|
|
||||||
`Skipping lazy hydration for component '${getComponentName(resolvedComp!)}': ` +
|
|
||||||
`it was updated before lazy hydration performed.`,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hydrate()
|
|
||||||
}
|
|
||||||
const teardown = hydrateStrategy(performHydrate, cb =>
|
const teardown = hydrateStrategy(performHydrate, cb =>
|
||||||
forEachElement(el, cb),
|
forEachElement(el, cb),
|
||||||
)
|
)
|
||||||
if (teardown) {
|
if (teardown) {
|
||||||
;(instance.bum || (instance.bum = [])).push(teardown)
|
;(instance.bum || (instance.bum = [])).push(teardown)
|
||||||
}
|
}
|
||||||
;(instance.u || (instance.u = [])).push(() => (patched = true))
|
|
||||||
}
|
}
|
||||||
: hydrate
|
: performHydrate
|
||||||
if (resolvedComp) {
|
if (resolvedComp) {
|
||||||
doHydrate()
|
doHydrate()
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue