瀏覽代碼

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

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
白雾三语 2 年之前
父節點
當前提交
6eaf4b651b

+ 50 - 0
packages/runtime-vapor/__tests__/component.spec.ts

@@ -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('')
+  })
+})

+ 5 - 3
packages/runtime-vapor/src/dom.ts

@@ -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)]) }
 }
 

+ 1 - 0
packages/runtime-vapor/src/index.ts

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

+ 5 - 2
packages/runtime-vapor/src/render.ts

@@ -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)

+ 2 - 2
packages/runtime-vapor/src/template.ts

@@ -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
     }
   }
 }

+ 5 - 0
playground/src/no-script.js

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