mirror of https://github.com/vuejs/core.git
wip(vapor): fix component unmount when not at block root level
This commit is contained in:
parent
99d70ddd31
commit
bcd2eb7fd8
|
@ -21,8 +21,10 @@ import {
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import {
|
import {
|
||||||
createComponent,
|
createComponent,
|
||||||
|
createFor,
|
||||||
createIf,
|
createIf,
|
||||||
createTextNode,
|
createTextNode,
|
||||||
|
insert,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
setText,
|
setText,
|
||||||
template,
|
template,
|
||||||
|
@ -526,4 +528,90 @@ describe('api: lifecycle hooks', () => {
|
||||||
expect(handleUpdated).toHaveBeenCalledTimes(1)
|
expect(handleUpdated).toHaveBeenCalledTimes(1)
|
||||||
expect(handleUpdatedChild).toHaveBeenCalledTimes(1)
|
expect(handleUpdatedChild).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('unmount hooks when nested in if block', async () => {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('<div><span></span></div><!--if-->')
|
||||||
|
})
|
||||||
|
const fn2 = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('<!--if-->')
|
||||||
|
})
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
const n0 = createIf(
|
||||||
|
() => toggle.value,
|
||||||
|
() => {
|
||||||
|
const n1 = document.createElement('div')
|
||||||
|
const n2 = createComponent(Child)
|
||||||
|
insert(n2, n1)
|
||||||
|
return n1
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onBeforeUnmount(fn)
|
||||||
|
onUnmounted(fn2)
|
||||||
|
|
||||||
|
const t0 = template('<span></span>')
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
expect(fn2).toHaveBeenCalledTimes(1)
|
||||||
|
expect(host.innerHTML).toBe('<!--if-->')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('unmount hooks when nested in for blocks', async () => {
|
||||||
|
const list = ref([1])
|
||||||
|
const fn = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('<div><span></span></div><!--for-->')
|
||||||
|
})
|
||||||
|
const fn2 = vi.fn(() => {
|
||||||
|
expect(host.innerHTML).toBe('<!--for-->')
|
||||||
|
})
|
||||||
|
const { render, host } = define({
|
||||||
|
setup() {
|
||||||
|
const n0 = createFor(
|
||||||
|
() => list.value,
|
||||||
|
() => {
|
||||||
|
const n1 = document.createElement('div')
|
||||||
|
const n2 = createComponent(Child)
|
||||||
|
insert(n2, n1)
|
||||||
|
return n1
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onBeforeUnmount(fn)
|
||||||
|
onUnmounted(fn2)
|
||||||
|
|
||||||
|
const t0 = template('<span></span>')
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
list.value.pop()
|
||||||
|
await nextTick()
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
expect(fn2).toHaveBeenCalledTimes(1)
|
||||||
|
expect(host.innerHTML).toBe('<!--for-->')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -75,7 +75,7 @@ export const createFor = (
|
||||||
let newBlocks: ForBlock[]
|
let newBlocks: ForBlock[]
|
||||||
let parent: ParentNode | undefined | null
|
let parent: ParentNode | undefined | null
|
||||||
const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
||||||
const ref = new VaporFragment(oldBlocks)
|
const frag = new VaporFragment(oldBlocks)
|
||||||
const instance = currentInstance!
|
const instance = currentInstance!
|
||||||
|
|
||||||
if (__DEV__ && !instance) {
|
if (__DEV__ && !instance) {
|
||||||
|
@ -265,9 +265,9 @@ export const createFor = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.nodes = [(oldBlocks = newBlocks)]
|
frag.nodes = [(oldBlocks = newBlocks)]
|
||||||
if (parentAnchor) {
|
if (parentAnchor) {
|
||||||
ref.nodes.push(parentAnchor)
|
frag.nodes.push(parentAnchor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,12 +338,12 @@ export const createFor = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const unmount = ({ nodes, scope }: ForBlock) => {
|
const unmount = ({ nodes, scope }: ForBlock) => {
|
||||||
removeBlock(nodes, parent!)
|
|
||||||
scope && scope.stop()
|
scope && scope.stop()
|
||||||
|
removeBlock(nodes, parent!)
|
||||||
}
|
}
|
||||||
|
|
||||||
once ? renderList() : renderEffect(renderList)
|
once ? renderList() : renderEffect(renderList)
|
||||||
return ref
|
return frag
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createForSlots(
|
export function createForSlots(
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class VaporFragment {
|
||||||
nodes: Block
|
nodes: Block
|
||||||
anchor?: Node
|
anchor?: Node
|
||||||
insert?: (parent: ParentNode, anchor: Node | null) => void
|
insert?: (parent: ParentNode, anchor: Node | null) => void
|
||||||
remove?: () => void
|
remove?: (parent?: ParentNode) => void
|
||||||
|
|
||||||
constructor(nodes: Block) {
|
constructor(nodes: Block) {
|
||||||
this.nodes = nodes
|
this.nodes = nodes
|
||||||
|
@ -139,16 +139,12 @@ export function prepend(parent: ParentNode, ...blocks: Block[]): void {
|
||||||
* during each root remove call, and update their children list by filtering
|
* during each root remove call, and update their children list by filtering
|
||||||
* unmounted children
|
* unmounted children
|
||||||
*/
|
*/
|
||||||
export let parentsWithUnmountedChildren: Set<VaporComponentInstance> | null =
|
// export let parentsWithUnmountedChildren: Set<VaporComponentInstance> | null =
|
||||||
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 && parent.removeChild(block)
|
||||||
} else if (isVaporComponent(block)) {
|
} else if (isVaporComponent(block)) {
|
||||||
unmountComponent(block, parent)
|
unmountComponent(block, parent)
|
||||||
} else if (isArray(block)) {
|
} else if (isArray(block)) {
|
||||||
|
@ -158,7 +154,7 @@ export function remove(block: Block, parent: ParentNode): void {
|
||||||
} else {
|
} else {
|
||||||
// fragment
|
// fragment
|
||||||
if (block.remove) {
|
if (block.remove) {
|
||||||
block.remove()
|
block.remove(parent)
|
||||||
} else {
|
} else {
|
||||||
remove(block.nodes, parent)
|
remove(block.nodes, parent)
|
||||||
}
|
}
|
||||||
|
@ -167,12 +163,6 @@ 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
|
||||||
type ComponentInternalOptions,
|
type ComponentInternalOptions,
|
||||||
type ComponentPropsOptions,
|
type ComponentPropsOptions,
|
||||||
EffectScope,
|
EffectScope,
|
||||||
|
@ -26,29 +25,17 @@ import {
|
||||||
unregisterHMR,
|
unregisterHMR,
|
||||||
warn,
|
warn,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import {
|
import { type Block, insert, isBlock, remove } from './block'
|
||||||
type Block,
|
|
||||||
insert,
|
|
||||||
isBlock,
|
|
||||||
parentsWithUnmountedChildren,
|
|
||||||
remove,
|
|
||||||
} from './block'
|
|
||||||
import {
|
import {
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
markRaw,
|
markRaw,
|
||||||
|
onScopeDispose,
|
||||||
pauseTracking,
|
pauseTracking,
|
||||||
proxyRefs,
|
proxyRefs,
|
||||||
resetTracking,
|
resetTracking,
|
||||||
unref,
|
unref,
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import {
|
import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared'
|
||||||
EMPTY_ARR,
|
|
||||||
EMPTY_OBJ,
|
|
||||||
invokeArrayFns,
|
|
||||||
isFunction,
|
|
||||||
isString,
|
|
||||||
remove as removeItem,
|
|
||||||
} from '@vue/shared'
|
|
||||||
import {
|
import {
|
||||||
type DynamicPropsSource,
|
type DynamicPropsSource,
|
||||||
type RawProps,
|
type RawProps,
|
||||||
|
@ -264,6 +251,8 @@ export function createComponent(
|
||||||
endMeasure(instance, 'init')
|
endMeasure(instance, 'init')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onScopeDispose(() => unmountComponent(instance), true)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,8 +289,6 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
||||||
type: VaporComponent
|
type: VaporComponent
|
||||||
root: GenericComponentInstance | null
|
root: GenericComponentInstance | null
|
||||||
parent: GenericComponentInstance | null
|
parent: GenericComponentInstance | null
|
||||||
children: VaporComponentInstance[]
|
|
||||||
vdomChildren?: ComponentInternalInstance[]
|
|
||||||
appContext: GenericAppContext
|
appContext: GenericAppContext
|
||||||
|
|
||||||
block: Block
|
block: Block
|
||||||
|
@ -379,12 +366,8 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
||||||
this.type = comp
|
this.type = comp
|
||||||
this.parent = currentInstance
|
this.parent = currentInstance
|
||||||
this.root = currentInstance ? currentInstance.root : this
|
this.root = currentInstance ? currentInstance.root : this
|
||||||
this.children = []
|
|
||||||
|
|
||||||
if (currentInstance) {
|
if (currentInstance) {
|
||||||
if (isVaporComponent(currentInstance)) {
|
|
||||||
currentInstance.children.push(this)
|
|
||||||
}
|
|
||||||
this.appContext = currentInstance.appContext
|
this.appContext = currentInstance.appContext
|
||||||
this.provides = currentInstance.provides
|
this.provides = currentInstance.provides
|
||||||
this.ids = currentInstance.ids
|
this.ids = currentInstance.ids
|
||||||
|
@ -523,40 +506,13 @@ export function unmountComponent(
|
||||||
|
|
||||||
instance.scope.stop()
|
instance.scope.stop()
|
||||||
|
|
||||||
for (const c of instance.children) {
|
|
||||||
unmountComponent(c)
|
|
||||||
}
|
|
||||||
instance.children = EMPTY_ARR as any
|
|
||||||
|
|
||||||
if (instance.vdomChildren) {
|
|
||||||
const unmount = instance.appContext.vapor!.vdomUnmount
|
|
||||||
for (const c of instance.vdomChildren) {
|
|
||||||
unmount(c, null)
|
|
||||||
}
|
|
||||||
instance.vdomChildren = 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
|
|
||||||
instance.parent = null
|
|
||||||
if (isVaporComponent(parentInstance)) {
|
|
||||||
if (parentsWithUnmountedChildren) {
|
|
||||||
// for optimize children removal
|
|
||||||
parentsWithUnmountedChildren.add(parentInstance)
|
|
||||||
} else {
|
|
||||||
removeItem(parentInstance.children, instance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instance.um) {
|
if (instance.um) {
|
||||||
queuePostFlushCb(() => invokeArrayFns(instance.um!))
|
queuePostFlushCb(() => invokeArrayFns(instance.um!))
|
||||||
}
|
}
|
||||||
instance.isUnmounted = true
|
instance.isUnmounted = true
|
||||||
} else if (parentNode) {
|
}
|
||||||
|
|
||||||
|
if (parentNode) {
|
||||||
remove(instance.block, parentNode)
|
remove(instance.block, parentNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
unmountComponent,
|
unmountComponent,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { type Block, VaporFragment, insert, remove } from './block'
|
import { type Block, VaporFragment, insert, remove } from './block'
|
||||||
import { extend, isFunction, remove as removeItem } from '@vue/shared'
|
import { extend, isFunction } from '@vue/shared'
|
||||||
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
|
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
|
||||||
import type { RawSlots, VaporSlot } from './componentSlots'
|
import type { RawSlots, VaporSlot } from './componentSlots'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
|
@ -116,17 +116,14 @@ function createVDOMComponent(
|
||||||
undefined,
|
undefined,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
;(parentInstance.vdomChildren || (parentInstance.vdomChildren = [])).push(
|
// TODO register unmount with onScopeDispose
|
||||||
vnode.component!,
|
|
||||||
)
|
|
||||||
isMounted = true
|
isMounted = true
|
||||||
} else {
|
} else {
|
||||||
// TODO move
|
// TODO move
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
frag.remove = () => {
|
frag.remove = parentNode => {
|
||||||
internals.umt(vnode.component!, null, true)
|
internals.umt(vnode.component!, null, !!parentNode)
|
||||||
removeItem(parentInstance.vdomChildren!, vnode.component)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return frag
|
return frag
|
||||||
|
@ -144,11 +141,9 @@ function renderVDOMSlot(
|
||||||
|
|
||||||
let isMounted = false
|
let isMounted = false
|
||||||
let fallbackNodes: Block | undefined
|
let fallbackNodes: Block | undefined
|
||||||
let parentNode: ParentNode
|
|
||||||
let oldVNode: VNode | null = null
|
let oldVNode: VNode | null = null
|
||||||
|
|
||||||
frag.insert = (parent, anchor) => {
|
frag.insert = (parentNode, anchor) => {
|
||||||
parentNode = parent
|
|
||||||
if (!isMounted) {
|
if (!isMounted) {
|
||||||
renderEffect(() => {
|
renderEffect(() => {
|
||||||
const vnode = renderSlot(
|
const vnode = renderSlot(
|
||||||
|
@ -169,7 +164,7 @@ function renderVDOMSlot(
|
||||||
if (oldVNode) {
|
if (oldVNode) {
|
||||||
internals.um(oldVNode, parentComponent as any, null, true)
|
internals.um(oldVNode, parentComponent as any, null, true)
|
||||||
}
|
}
|
||||||
insert((fallbackNodes = fallback(props)), parent, anchor)
|
insert((fallbackNodes = fallback(props)), parentNode, anchor)
|
||||||
}
|
}
|
||||||
oldVNode = null
|
oldVNode = null
|
||||||
}
|
}
|
||||||
|
@ -179,7 +174,7 @@ function renderVDOMSlot(
|
||||||
// TODO move
|
// TODO move
|
||||||
}
|
}
|
||||||
|
|
||||||
frag.remove = () => {
|
frag.remove = parentNode => {
|
||||||
if (fallbackNodes) {
|
if (fallbackNodes) {
|
||||||
remove(fallbackNodes, parentNode)
|
remove(fallbackNodes, parentNode)
|
||||||
} else if (oldVNode) {
|
} else if (oldVNode) {
|
||||||
|
|
Loading…
Reference in New Issue