fix: handling v-for on component with non-hydration node

This commit is contained in:
daiwei 2025-09-16 22:49:02 +08:00
parent 4fc0247d0d
commit db2f397e47
3 changed files with 68 additions and 6 deletions

View File

@ -1746,8 +1746,8 @@ describe('Vapor Mode hydration', () => {
<!--[-->
<!--[--><span>a</span><!--]-->
<!--[--><span>b</span><!--]-->
<!--[--><span>c</span><!--for--><!--]-->
</div>"
<!--[--><span>c</span><!--]-->
<!--for--></div>"
`,
)
@ -1759,8 +1759,8 @@ describe('Vapor Mode hydration', () => {
<!--[-->
<!--[--><span>a</span><!--]-->
<!--[--><span>b</span><!--]-->
<!--[--><span>c</span><span>d</span><!--slot--><!--for--><!--]-->
</div>"
<!--[--><span>c</span><!--]-->
<span>d</span><!--slot--><!--for--></div>"
`,
)
})
@ -1801,6 +1801,57 @@ describe('Vapor Mode hydration', () => {
`,
)
})
test('on component with non-hydration node', async () => {
const data = ref({ show: true, msg: 'foo' })
const { container } = await testHydration(
`<template>
<div>
<components.Child v-for="item in 2" :key="item"/>
</div>
</template>`,
{
Child: `<template>
<div>
<div>
<div v-if="data.show">{{ data.msg }}</div>
</div>
<span>non-hydration node</span>
</div>
</template>`,
},
data,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
<!--[--><div><div><div>foo</div><!--if--></div><span>non-hydration node</span></div><div><div><div>foo</div><!--if--></div><span>non-hydration node</span></div><!--for--></div>"
`,
)
data.value.msg = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
<!--[--><div><div><div>bar</div><!--if--></div><span>non-hydration node</span></div><div><div><div>bar</div><!--if--></div><span>non-hydration node</span></div><!--for--></div>"
`,
)
data.value.show = false
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
"<div>
<!--[--><div><div><!--if--></div><span>non-hydration node</span></div><div><div><!--if--></div><span>non-hydration node</span></div><!--for--></div>"
`)
data.value.show = true
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
"<div>
<!--[--><div><div><div>bar</div><!--if--></div><span>non-hydration node</span></div><div><div><div>bar</div><!--if--></div><span>non-hydration node</span></div><!--for--></div>"
`)
})
})
describe('slots', () => {

View File

@ -30,8 +30,9 @@ import {
isHydrating,
locateFragmentEndAnchor,
locateHydrationNode,
setCurrentHydrationNode,
} from './dom/hydration'
import { ForFragment, VaporFragment } from './fragment'
import { ForFragment, VaporFragment, findLastChild } from './fragment'
import {
insertionAnchor,
insertionParent,
@ -130,10 +131,20 @@ export const createFor = (
if (!isMounted) {
isMounted = true
for (let i = 0; i < newLength; i++) {
if (isHydrating && isComponent && i > 0) {
setCurrentHydrationNode(
findLastChild(newBlocks[i - 1].nodes)!.nextSibling,
)
}
mount(source, i)
}
if (isHydrating) {
if (isComponent) {
setCurrentHydrationNode(
findLastChild(newBlocks[newLength - 1].nodes)!.nextSibling,
)
}
parentAnchor = locateFragmentEndAnchor()!
if (__DEV__) {
if (!parentAnchor) {

View File

@ -157,7 +157,7 @@ export function normalizeAnchor(node: Block): Node | undefined {
if (node && node instanceof Node) {
return node
} else if (isArray(node)) {
return normalizeAnchor(node[0])
return normalizeAnchor(node[node.length - 1])
} else if (isVaporComponent(node)) {
return normalizeAnchor(node.block!)
} else {