fix(runtime-vapor): `unmountComponent` (#63)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
白雾三语 2023-12-15 01:23:17 +08:00 committed by GitHub
parent 9e031275d7
commit 6eaf4b651b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 7 deletions

View File

@ -0,0 +1,50 @@
import {
template,
children,
effect,
setText,
render,
getCurrentInstance,
ref,
unmountComponent,
} from '../src'
import type { ComponentInternalInstance } from '../src'
import { afterEach, beforeEach, describe, expect } from 'vitest'
import { defineComponent, nextTick } from '@vue/runtime-core'
let host: HTMLElement
const initHost = () => {
host = document.createElement('div')
host.setAttribute('id', 'host')
document.body.appendChild(host)
}
beforeEach(() => {
initHost()
})
afterEach(() => {
host.remove()
})
describe('component', () => {
test('unmountComponent', async () => {
const Comp = defineComponent({
setup() {
const count = ref(0)
const t0 = template('<div></div>')
const n0 = t0()
const {
0: [n1],
} = children(n0)
effect(() => {
setText(n1, void 0, count.value)
})
return n0
},
})
const instance = render(Comp as any, {}, '#host')
await nextTick()
expect(host.innerHTML).toBe('<div>0</div>')
unmountComponent(instance)
expect(host.innerHTML).toBe('')
})
})

View File

@ -42,7 +42,9 @@ export function append(parent: ParentBlock, ...nodes: Node[]) {
}
export function remove(block: Block, parent: ParentNode) {
if (block instanceof Node) {
if (block instanceof DocumentFragment) {
remove(Array.from(block.childNodes), parent)
} else if (block instanceof Node) {
parent.removeChild(block)
} else if (isArray(block)) {
for (const child of block) remove(child, parent)
@ -52,7 +54,7 @@ export function remove(block: Block, parent: ParentNode) {
}
}
export function setText(el: Element, oldVal: any, newVal: any) {
export function setText(el: Node, oldVal: any, newVal: any) {
if ((newVal = toDisplayString(newVal)) !== oldVal) {
el.textContent = newVal
}
@ -104,7 +106,7 @@ export function setDynamicProp(el: Element, key: string, val: any) {
}
type Children = Record<number, [ChildNode, Children]>
export function children(n: ChildNode): Children {
export function children(n: Node): Children {
return { ...Array.from(n.childNodes).map((n) => [n, children(n)]) }
}

View File

@ -44,3 +44,4 @@ export * from './scheduler'
export * from './directive'
export * from './dom'
export * from './directives/vShow'
export { getCurrentInstance, type ComponentInternalInstance } from './component'

View File

@ -54,12 +54,15 @@ export function mountComponent(
new Proxy({ _: instance }, PublicInstanceProxyHandlers),
)
const state = setupFn && setupFn(props, ctx)
let block: Block | null = null
if (state && '__isScriptSetup' in state) {
instance.setupState = proxyRefs(state)
return (instance.block = component.render(instance.proxy))
block = component.render(instance.proxy)
} else {
return (instance.block = state as Block)
block = state as Block
}
if (block instanceof DocumentFragment) block = Array.from(block.childNodes)
return (instance.block = block)
})!
invokeDirectiveHook(instance, 'beforeMount')
insert(block, instance.container)

View File

@ -1,4 +1,4 @@
export const template = (str: string): (() => Node) => {
export const template = (str: string): (() => DocumentFragment) => {
let cached = false
let node: DocumentFragment
return () => {
@ -14,7 +14,7 @@ export const template = (str: string): (() => Node) => {
// repeated renders: clone from cache. This is more performant and
// efficient when dealing with big lists where the template is repeated
// many times.
return node.cloneNode(true)
return node.cloneNode(true) as DocumentFragment
}
}
}

View File

@ -0,0 +1,5 @@
import { template } from '@vue/runtime-vapor'
export default () => {
return template('<div>')()
}