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>a</span><!--]-->
<!--[--><span>b</span><!--]--> <!--[--><span>b</span><!--]-->
<!--[--><span>c</span><!--for--><!--]--> <!--[--><span>c</span><!--]-->
</div>" <!--for--></div>"
`, `,
) )
@ -1759,8 +1759,8 @@ describe('Vapor Mode hydration', () => {
<!--[--> <!--[-->
<!--[--><span>a</span><!--]--> <!--[--><span>a</span><!--]-->
<!--[--><span>b</span><!--]--> <!--[--><span>b</span><!--]-->
<!--[--><span>c</span><span>d</span><!--slot--><!--for--><!--]--> <!--[--><span>c</span><!--]-->
</div>" <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', () => { describe('slots', () => {

View File

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

View File

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