wip(vapor): optimize unmounted children removal

This commit is contained in:
Evan You 2024-12-14 16:28:05 +08:00
parent 76e8d2c4d0
commit bcb9209c4c
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
2 changed files with 59 additions and 7 deletions

View File

@ -126,7 +126,19 @@ export function prepend(parent: ParentNode, ...blocks: Block[]): void {
while (i--) insert(blocks[i], parent, 0) while (i--) insert(blocks[i], parent, 0)
} }
/**
* Optimized children removal: record all parents with unmounted children
* during each root remove call, and update their children list by filtering
* unmounted children
*/
export let parentsWithUnmountedChildren: Set<VaporComponentInstance> | null =
null
export function remove(block: Block, parent: ParentNode): void { export function remove(block: Block, parent: ParentNode): void {
const isRoot = !parentsWithUnmountedChildren
if (isRoot) {
parentsWithUnmountedChildren = new Set()
}
if (block instanceof Node) { if (block instanceof Node) {
parent.removeChild(block) parent.removeChild(block)
} else if (isVaporComponent(block)) { } else if (isVaporComponent(block)) {
@ -143,4 +155,10 @@ export function remove(block: Block, parent: ParentNode): void {
;(block as DynamicFragment).scope!.stop() ;(block as DynamicFragment).scope!.stop()
} }
} }
if (isRoot) {
for (const i of parentsWithUnmountedChildren!) {
i.children = i.children.filter(n => !n.isUnmounted)
}
parentsWithUnmountedChildren = null
}
} }

View File

@ -25,7 +25,13 @@ import {
unregisterHMR, unregisterHMR,
warn, warn,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { type Block, insert, isBlock, remove } from './block' import {
type Block,
insert,
isBlock,
parentsWithUnmountedChildren,
remove,
} from './block'
import { import {
markRaw, markRaw,
pauseTracking, pauseTracking,
@ -33,7 +39,14 @@ import {
resetTracking, resetTracking,
unref, unref,
} from '@vue/reactivity' } from '@vue/reactivity'
import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared' import {
EMPTY_ARR,
EMPTY_OBJ,
invokeArrayFns,
isFunction,
isString,
remove as removeItem,
} from '@vue/shared'
import { import {
type DynamicPropsSource, type DynamicPropsSource,
type RawProps, type RawProps,
@ -465,24 +478,45 @@ export function mountComponent(
export function unmountComponent( export function unmountComponent(
instance: VaporComponentInstance, instance: VaporComponentInstance,
parent?: ParentNode, parentNode?: ParentNode,
): void { ): void {
if (instance.isMounted && !instance.isUnmounted) { if (instance.isMounted && !instance.isUnmounted) {
if (__DEV__ && instance.type.__hmrId) { if (__DEV__ && instance.type.__hmrId) {
unregisterHMR(instance) unregisterHMR(instance)
} }
if (instance.bum) invokeArrayFns(instance.bum) if (instance.bum) {
invokeArrayFns(instance.bum)
}
instance.scope.stop() instance.scope.stop()
for (const c of instance.children) { for (const c of instance.children) {
unmountComponent(c) unmountComponent(c)
} }
if (parent) remove(instance.block, parent) instance.children = EMPTY_ARR as any
if (parentNode) {
// root remove: need to both remove this instance's DOM nodes
// and also remove it from the parent's children list.
remove(instance.block, parentNode)
const parentInstance = instance.parent
if (isVaporComponent(parentInstance)) {
if (parentsWithUnmountedChildren) {
// for optimize children removal
parentsWithUnmountedChildren.add(parentInstance)
} else {
removeItem(parentInstance.children, instance)
}
instance.parent = null
}
}
if (instance.um) { if (instance.um) {
queuePostFlushCb(() => invokeArrayFns(instance.um!)) queuePostFlushCb(() => invokeArrayFns(instance.um!))
} }
instance.isUnmounted = true instance.isUnmounted = true
} else if (parent) { } else if (parentNode) {
remove(instance.block, parent) remove(instance.block, parentNode)
} }
} }