mirror of https://github.com/vuejs/core.git
fix(hydration): skip dynamic children in __child
This commit is contained in:
parent
e903dec682
commit
3f3480c05b
|
@ -157,7 +157,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
|||
const _component_Comp = _resolveComponent("Comp")
|
||||
const n0 = t0()
|
||||
const n3 = t1()
|
||||
const n2 = _child(n3)
|
||||
const n2 = _child(n3, 1)
|
||||
_setInsertionState(n3, 0)
|
||||
const n1 = _createComponentWithFallback(_component_Comp)
|
||||
_renderEffect(() => {
|
||||
|
|
|
@ -82,11 +82,15 @@ export function genChildren(
|
|||
pushBlock(...genCall(helper('nthChild'), from, String(elementIndex)))
|
||||
}
|
||||
} else {
|
||||
// offset is used to determine the child during hydration.
|
||||
// if offset is not 0, we need to specify the offset to skip the dynamic
|
||||
// children and get the correct child.
|
||||
let childOffset = offset === 0 ? undefined : `${Math.abs(offset)}`
|
||||
if (elementIndex === 0) {
|
||||
pushBlock(...genCall(helper('child'), from))
|
||||
pushBlock(...genCall(helper('child'), from, childOffset))
|
||||
} else {
|
||||
// check if there's a node that we can reuse from
|
||||
let init = genCall(helper('child'), from)
|
||||
let init = genCall(helper('child'), from, childOffset)
|
||||
if (elementIndex === 1) {
|
||||
init = genCall(helper('next'), init)
|
||||
} else if (elementIndex > 1) {
|
||||
|
|
|
@ -2176,6 +2176,43 @@ describe('Vapor Mode hydration', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('mixed consecutive slot and element', async () => {
|
||||
const data = reactive({
|
||||
text: 'foo',
|
||||
msg: 'hi',
|
||||
})
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<components.Child>
|
||||
<template #foo><span>{{data.text}}</span></template>
|
||||
<template #bar><span>bar</span></template>
|
||||
</components.Child>
|
||||
</template>`,
|
||||
{
|
||||
Child: `<template><div><slot name="foo"/><slot name="bar"/><div>{{data.msg}}</div></div></template>`,
|
||||
},
|
||||
data,
|
||||
)
|
||||
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<div>hi</div>` +
|
||||
`</div>`,
|
||||
)
|
||||
|
||||
data.msg = 'bar'
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<div>` +
|
||||
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
|
||||
`<div>bar</div>` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test('mixed slot and element', async () => {
|
||||
const data = reactive({
|
||||
text: 'foo',
|
||||
|
|
|
@ -41,7 +41,7 @@ export function _child(node: ParentNode): Node {
|
|||
*
|
||||
* Client Compiled Code (Simplified):
|
||||
* const n2 = t0() // n2 = `<div> </div>`
|
||||
* const n1 = _child(n2) // n1 = text node
|
||||
* const n1 = _child(n2, 1) // n1 = text node
|
||||
* // ... slot creation ...
|
||||
* _renderEffect(() => _setText(n1, _ctx.msg))
|
||||
*
|
||||
|
@ -49,18 +49,18 @@ export function _child(node: ParentNode): Node {
|
|||
*
|
||||
* Hydration Mismatch:
|
||||
* - During hydration, `n2` refers to the SSR `<div>`.
|
||||
* - `_child(n2)` would return `<!--[-->`.
|
||||
* - `_child(n2, 1)` would return `<!--[-->`.
|
||||
* - The client code expects `n1` to be the text node, but gets the comment.
|
||||
* The subsequent `_setText(n1, ...)` would fail or target the wrong node.
|
||||
*
|
||||
* Solution (`__child`):
|
||||
* - `__child(n2)` is used during hydration. It skips the SSR fragment anchors
|
||||
* (`<!--[-->...<!--]-->`) and any other non-content nodes to find the
|
||||
* "Actual Text Node", correctly matching the client's expectation for `n1`.
|
||||
* - `__child(n2, offset)` is used during hydration. It skips the dynamic children
|
||||
* to find the "Actual Text Node", correctly matching the client's expectation
|
||||
* for `n1`.
|
||||
*/
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function __child(node: ParentNode): Node {
|
||||
let n = node.firstChild!
|
||||
export function __child(node: ParentNode, offset?: number): Node {
|
||||
let n = offset ? __nthChild(node, offset) : node.firstChild!
|
||||
|
||||
if (isComment(n, '[')) {
|
||||
n = locateEndAnchor(n)!.nextSibling!
|
||||
|
@ -162,8 +162,8 @@ type DelegatedFunction<T extends (...args: any[]) => any> = T & {
|
|||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export const child: DelegatedFunction<typeof _child> = node => {
|
||||
return child.impl(node)
|
||||
export const child: DelegatedFunction<typeof __child> = (node, offset) => {
|
||||
return child.impl(node, offset)
|
||||
}
|
||||
child.impl = _child
|
||||
|
||||
|
|
Loading…
Reference in New Issue