vue createVNode render 问题

143 阅读2分钟

之前做了一个查看文件源代码的组件,想着使用组件不方便,所以做成了js调用的方式

使用

showSourceCode('/src/directives/module/resize.ts')

功能实现

<template>
  <el-dialog
    :modelValue="show"
    align-center
    append-to-body
    destroy-on-close
    :before-close="close"
    modal-class="source-code-dialog"
    v-bind="dialogProps">
    <template #header>
      <div>
        代码地址:<span class="font-bold ml-5">{{ codePath }}</span>
      </div>
    </template>
    <section class="code-area overflow-auto no-scroll">
      <Code :html="getSourceCode" :language="dialogProps.language" />
    </section>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="close" type="danger">关闭</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script lang="ts" setup>
import Code from '@/components/code/index.vue'
import { IDialogProps } from '.'
const codePath = ref('')
const show = ref(false)
const dialogProps = ref<IDialogProps & { language: 'js' | 'ts' | 'json' | string }>({
  width: 900,
  language: 'ts',
})

const getSourceCode = computed(() => {
  const allFiles = import.meta.glob(['@/directives/**', '@/components/**'], {
    eager: true,
    as: 'raw',
  })
  // console.log(`allFiles ==>`, allFiles)
  return allFiles[codePath.value] || '```代码地址不存在```'
})

const close = () => {
  show.value = false
}

const open = (_codePath: string, _dialogProps?: IDialogProps) => {
  codePath.value = _codePath
  if (_dialogProps) {
    dialogProps.value = {
      ...dialogProps.value,
      ..._dialogProps,
    }
  }
  show.value = true
  return true
}

defineExpose({
  close,
  open,
})
</script>
<style lang="scss">
.source-code-dialog {
  font-style: italic;
  .el-overlay-dialog {
    .el-dialog {
      @apply rounded-xl;
      .el-dialog__body {
        padding: 0 15px;
        font-size: 15px;
        .code-area {
          max-height: calc(100vh - 150px);
          overflow: auto;
        }
      }
    }
  }
}

</style>

import { createVNode, render, createApp } from 'vue'
import Index from './index.vue'
import { ElDialog } from 'element-plus'

export type IDialogProps = InstanceType<typeof ElDialog>['$props']

/**
 * 查看源代码
 * @param codePath
 *
 */
export const showSourceCode = async (codePath: string, dialogProps: IDialogProps = {}) => {
  const excuteFn = () => {
    const vNode = createVNode(Index)
    render(vNode, document.body)
    const { open, close } = vNode.component?.exposed as InstanceType<typeof Index>
    open(codePath, dialogProps)
  }
  try {
    excuteFn()
  } catch (error) {
    console.log(`error ==>`, error)
    excuteFn()
  }
}

组件正常显示

出现问题 (DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.)

error ==> DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
    at insert (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:9134:12)
    at Object.process (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:7873:7)
    at patch (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:6448:16)
    at ReactiveEffect.componentUpdateFn [as fn] (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:7106:11)
    at ReactiveEffect.run (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:423:19)
    at instance.update (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:7212:52)
    at setupRenderEffect (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:7220:5)
    at mountComponent (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:7010:5)
    at processComponent (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:6963:9)
    at patch (http://192.168.5.240:9527/node_modules/.vite/deps/chunk-M4WZCJL6.js?v=ec5b445d:6436:11)

这个问题我搜了半天不知道问题出在哪里 因为这样js调用的方式我在两年前就写过一模一样的示例,但是没报错

查询问题的原因的出在插入dom的问题,不知道vue更新了什么导致使用这一api报错

解决方法 添加<div id="dialog-container"></div>

<body>
  <div id="app"></div>
  <div id="dialog-container"></div>
  <div id="loading-mask">
    <div class="loading-wrapper">
       <span class="loading-dot loading-dot-spin">
        <i></i>
        <i></i>
        <i></i>
        <i></i>
      </span>
    </div>
  </div>
  <script type="module" src="./src/main.ts"></script>
</body>
/**
 * 查看源代码
 * @param codePath
 *
 */
export const showSourceCode = async (codePath: string, dialogProps: IDialogProps = {}) => {
  const excuteFn = () => {
    const vNode = createVNode(Index)
    render(vNode, document.getElementById('dialog-container'))
    const { open, close } = vNode.component?.exposed as InstanceType<typeof Index>
    open(codePath, dialogProps)
  }
  try {
    excuteFn()
  } catch (error) {
    console.log(`error ==>`, error)
    excuteFn()
  }
}

问题解决了

image.png

猜测是因为 **teleport** 导致的