Skip to content

Commit a9269c6

Browse files
authored
fix(hydration): prevent lazy hydration for updated components (#13511)
close #13510
1 parent 00695a5 commit a9269c6

File tree

2 files changed

+78
-13
lines changed

2 files changed

+78
-13
lines changed

‎packages/runtime-core/__tests__/hydration.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,69 @@ describe('SSR hydration', () => {
11601160
)
11611161
})
11621162

1163+
// #13510
1164+
test('update async component after parent mount before async component resolve', async () => {
1165+
const Comp = {
1166+
props: ['toggle'],
1167+
render(this: any) {
1168+
return h('h1', [
1169+
this.toggle ? 'Async component' : 'Updated async component',
1170+
])
1171+
},
1172+
}
1173+
let serverResolve: any
1174+
let AsyncComp = defineAsyncComponent(
1175+
() =>
1176+
new Promise(r => {
1177+
serverResolve = r
1178+
}),
1179+
)
1180+
1181+
const toggle = ref(true)
1182+
const App = {
1183+
setup() {
1184+
onMounted(() => {
1185+
// change state, after mount and before async component resolve
1186+
nextTick(() => (toggle.value = false))
1187+
})
1188+
1189+
return () => {
1190+
return h(AsyncComp, { toggle: toggle.value })
1191+
}
1192+
},
1193+
}
1194+
1195+
// server render
1196+
const htmlPromise = renderToString(h(App))
1197+
serverResolve(Comp)
1198+
const html = await htmlPromise
1199+
expect(html).toMatchInlineSnapshot(`"<h1>Async component</h1>"`)
1200+
1201+
// hydration
1202+
let clientResolve: any
1203+
AsyncComp = defineAsyncComponent(
1204+
() =>
1205+
new Promise(r => {
1206+
clientResolve = r
1207+
}),
1208+
)
1209+
1210+
const container = document.createElement('div')
1211+
container.innerHTML = html
1212+
createSSRApp(App).mount(container)
1213+
1214+
// resolve
1215+
clientResolve(Comp)
1216+
await new Promise(r => setTimeout(r))
1217+
1218+
// prevent lazy hydration since the component has been patched
1219+
expect('Skipping lazy hydration for component').toHaveBeenWarned()
1220+
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
1221+
expect(container.innerHTML).toMatchInlineSnapshot(
1222+
`"<h1>Updated async component</h1>"`,
1223+
)
1224+
})
1225+
11631226
test('hydrate safely when property used by async setup changed before render', async () => {
11641227
const toggle = ref(true)
11651228

‎packages/runtime-core/src/apiAsyncComponent.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,28 +123,30 @@ export function defineAsyncComponent<
123123

124124
__asyncHydrate(el, instance, hydrate) {
125125
let patched = false
126+
;(instance.bu || (instance.bu = [])).push(() => (patched = true))
127+
const performHydrate = () => {
128+
// skip hydration if the component has been patched
129+
if (patched) {
130+
if (__DEV__) {
131+
warn(
132+
`Skipping lazy hydration for component '${getComponentName(resolvedComp!) || resolvedComp!.__file}': ` +
133+
`it was updated before lazy hydration performed.`,
134+
)
135+
}
136+
return
137+
}
138+
hydrate()
139+
}
126140
const doHydrate = hydrateStrategy
127141
? () => {
128-
const performHydrate = () => {
129-
// skip hydration if the component has been patched
130-
if (__DEV__ && patched) {
131-
warn(
132-
`Skipping lazy hydration for component '${getComponentName(resolvedComp!)}': ` +
133-
`it was updated before lazy hydration performed.`,
134-
)
135-
return
136-
}
137-
hydrate()
138-
}
139142
const teardown = hydrateStrategy(performHydrate, cb =>
140143
forEachElement(el, cb),
141144
)
142145
if (teardown) {
143146
;(instance.bum || (instance.bum = [])).push(teardown)
144147
}
145-
;(instance.u || (instance.u = [])).push(() => (patched = true))
146148
}
147-
: hydrate
149+
: performHydrate
148150
if (resolvedComp) {
149151
doHydrate()
150152
} else {

0 commit comments

Comments
 (0)