feat(runtime-core): add unwrapFragment to flatten nested Fragment nodes in vnode arrays

This commit is contained in:
baozhangjie 2025-07-01 14:35:24 +08:00
parent ba391f5fdf
commit 582f16accf
4 changed files with 71 additions and 0 deletions

View File

@ -0,0 +1,48 @@
import { describe, expect, it } from 'vitest'
import { Fragment, type VNode, h, unwrapFragment } from '../src/index'
describe('unwrapFragment', () => {
it('returns empty array if input is undefined or empty', () => {
expect(unwrapFragment(undefined)).toEqual([])
expect(unwrapFragment([])).toEqual([])
})
it('returns same array if no Fragment present', () => {
const vnode1 = h('div')
const vnode2 = h('span')
const input = [vnode1, vnode2]
const result = unwrapFragment(input)
expect(result).toEqual(input)
})
it('unwraps single level Fragment', () => {
const children = [h('div', 'a'), h('div', 'b')]
const fragmentVNode: VNode = h(Fragment, null, children)
const input = [fragmentVNode]
const result = unwrapFragment(input)
expect(result).toHaveLength(2)
expect(result).toEqual(children)
})
it('unwraps nested Fragments recursively', () => {
const innerChildren = [h('span', 'x'), h('span', 'y')]
const innerFragment = h(Fragment, null, innerChildren)
const outerChildren = [innerFragment, h('div', 'z')]
const outerFragment = h(Fragment, null, outerChildren)
const input = [outerFragment]
const result = unwrapFragment(input)
// Should flatten all fragments recursively
expect(result).toHaveLength(3)
expect(result).toEqual([...innerChildren, outerChildren[1]])
})
it('unwraps mixed array with Fragment and non-Fragment vnode', () => {
const children = [h('li', 'item1'), h('li', 'item2')]
const fragmentVNode = h(Fragment, null, children)
const nonFragmentVNode = h('p', 'paragraph')
const input = [fragmentVNode, nonFragmentVNode]
const result = unwrapFragment(input)
expect(result).toHaveLength(3)
expect(result).toEqual([...children, nonFragmentVNode])
})
})

View File

@ -125,6 +125,8 @@ export { withDirectives } from './directives'
// SSR context
export { useSSRContext, ssrContextKey } from './helpers/useSsrContext'
export { unwrapFragment } from './unwrapFragment'
// Custom Renderer API ---------------------------------------------------------
export { createRenderer, createHydrationRenderer } from './renderer'

View File

@ -0,0 +1,17 @@
import { type VNode, isFragmentVNode } from './vnode'
/**
* vnode Fragment
*/
export const unwrapFragment = (vnodes: VNode[] | undefined): VNode[] => {
if (!vnodes) return []
const result: VNode[] = []
for (const vnode of vnodes) {
if (isFragmentVNode(vnode)) {
result.push(...unwrapFragment(vnode.children as VNode[]))
} else {
result.push(vnode)
}
}
return result
}

View File

@ -387,6 +387,10 @@ export function isVNode(value: any): value is VNode {
return value ? value.__v_isVNode === true : false
}
export function isFragmentVNode(vnode: VNode): vnode is VNode {
return !!vnode && vnode.type === Fragment
}
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
if (__DEV__ && n2.shapeFlag & ShapeFlags.COMPONENT && n1.component) {
const dirtyInstances = hmrDirtyComponents.get(n2.type as ConcreteComponent)