wip: render fallback nodes for empty vfor
ci / test (push) Waiting to run Details
ci / continuous-release (push) Waiting to run Details

This commit is contained in:
daiwei 2025-07-23 18:07:22 +08:00
parent e28b96bea8
commit dd311402a9
2 changed files with 47 additions and 21 deletions

View File

@ -15,8 +15,10 @@ import { isArray, isObject, isString } from '@vue/shared'
import { createComment, createTextNode } from './dom/node'
import {
type Block,
ForFragment,
VaporFragment,
insert,
remove,
remove as removeBlock,
} from './block'
import { warn } from '@vue/runtime-dom'
@ -77,7 +79,7 @@ export const createFor = (
setup?: (_: {
createSelector: (source: () => any) => (cb: () => void) => void
}) => void,
): VaporFragment => {
): ForFragment => {
const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor
if (isHydrating) {
@ -94,7 +96,7 @@ export const createFor = (
let currentKey: any
// TODO handle this in hydration
const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
const frag = new VaporFragment(oldBlocks)
const frag = new ForFragment(oldBlocks)
const instance = currentInstance!
const canUseFastRemove = !!(flags & VaporVForFlags.FAST_REMOVE)
const isComponent = !!(flags & VaporVForFlags.IS_COMPONENT)
@ -123,6 +125,14 @@ export const createFor = (
} else {
parent = parent || parentAnchor!.parentNode
if (!oldLength) {
// remove fallback nodes if needed
if (frag.fallback) {
const fallbackNodes = frag.nodes[0] as any
if (fallbackNodes) {
remove(fallbackNodes, parent!)
}
}
// fast path for all new
for (let i = 0; i < newLength; i++) {
mount(source, i)
@ -329,6 +339,11 @@ export const createFor = (
frag.nodes.push(parentAnchor)
}
// render fallback nodes if needed
if (isMounted && frag.fallback && newLength === 0) {
insert((frag.nodes[0] = frag.fallback()), parent!, parentAnchor)
}
setActiveSub(prevSub)
}

View File

@ -18,17 +18,24 @@ export type Block =
export type BlockFn = (...args: any[]) => Block
export class VaporFragment {
nodes: Block
export class VaporFragment<T extends Block = Block> {
nodes: T
anchor?: Node
insert?: (parent: ParentNode, anchor: Node | null) => void
remove?: (parent?: ParentNode) => void
fallback?: BlockFn
constructor(nodes: Block) {
constructor(nodes: T) {
this.nodes = nodes
}
}
export class ForFragment extends VaporFragment<Block[]> {
constructor(nodes: Block[]) {
super(nodes)
}
}
export class DynamicFragment extends VaporFragment {
anchor: Node
scope: EffectScope | undefined
@ -67,14 +74,16 @@ export class DynamicFragment extends VaporFragment {
if (this.fallback && !isValidBlock(this.nodes)) {
parent && remove(this.nodes, parent)
// handle nested dynamic fragment
if (isFragment(this.nodes)) {
renderFallback(this.nodes, this.fallback, key)
} else {
this.nodes =
(this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
[]
}
const scope = this.scope || (this.scope = new EffectScope())
scope.run(() => {
// handle nested fragment
if (isFragment(this.nodes)) {
renderFallback(this.nodes, this.fallback!)
} else {
this.nodes = this.fallback!() || []
}
})
parent && insert(this.nodes, parent, this.anchor)
}
@ -82,19 +91,21 @@ export class DynamicFragment extends VaporFragment {
}
}
function renderFallback(
fragment: VaporFragment,
fallback: BlockFn,
key: any,
): void {
function renderFallback(fragment: VaporFragment, fallback: BlockFn): void {
if (!fragment.fallback) fragment.fallback = fallback
if (fragment instanceof DynamicFragment) {
const nodes = fragment.nodes
if (isFragment(nodes)) {
renderFallback(nodes, fallback, key)
renderFallback(nodes, fallback)
} else {
if (!fragment.fallback) fragment.fallback = fallback
fragment.update(fragment.fallback, key)
fragment.update(fragment.fallback)
}
} else if (fragment instanceof ForFragment) {
// TODO handle nested ForFragment
fragment.nodes[0] = fallback() || []
} else {
// vdom slots
}
}