vue3 动态挂载组件时如何继承上下文?

如何做才能使 render(vnode,**) 拥有和 <component :is="vnode" /> 相同的上下文?

<script lang="ts" setup>
import { h, render } from "vue";

const vnode = h("div");
requestAnimationFrame(() => {
  render(vnode, document.querySelector(".content-floating-box")!);
});
</script>

<template>
  <div class="content-floating-box"></div>
</template>
<script lang="ts" setup>
import { h } from "vue";
const vnode = h("div");
</script>

<template>
  <div class="content-floating-box">
    <component :is="vnode" />
  </div>
</template>

在位于 <app /> 内使用 render 插入 vnode 时会提示 injection "n-config-provider" not found.

<template>
  <!-- https://www.naiveui.com/zh-CN/light/docs/customize-theme -->
  <n-config-provider>
    <app />
  </n-config-provider>
</template>

无效尝试:

  • 手动继承 appContext
阅读 1.8k
3 个回答

封装useMont.js

// useMount.js
import { createVNode, render, getCurrentInstance } from 'vue'

/**
 * 动态挂载组件(继承上下文)
 * @returns {Function} mountComponent 函数
 */
export function useMountComponent() {
  const instance = getCurrentInstance()
  
  /**
   * 挂载组件
   * @param {Component} component - Vue 组件
   * @param {Object} options - 配置项
   * @param {Object} options.props - 组件 props
   * @param {Object} options.children - 子节点
   * @param {HTMLElement} options.container - 挂载容器(可选)
   * @returns {Object} { vnode, unmount }
   */
  return function mountComponent(component, options = {}) {
    const { props, children, container: customContainer } = options
    
    // 创建或使用指定容器
    const container = customContainer || document.createElement('div')
    if (!customContainer) {
      document.body.appendChild(container)
    }
    
    // 创建虚拟节点
    const vnode = createVNode(component, props, children)
    
    // 继承应用上下文(关键步骤)
    if (instance) {
      vnode.appContext = instance.appContext
    }
    
    // 渲染组件
    render(vnode, container)
    
    // 返回控制对象
    return {
      vnode,
      unmount: () => {
        render(null, container)
        if (!customContainer) {
          container.remove()
        }
      }
    }
  }
}

使用示例

<script setup>
import { useMountComponent } from './useMount'
import MyDialog from './MyDialog.vue'

const mountComponent = useMountComponent()

function openDialog() {
  const dialog = mountComponent(MyDialog, {
    props: {
      title: '提示',
      message: '这是一个动态挂载的弹窗',
      onConfirm: () => {
        console.log('确认')
        dialog.unmount()
      },
      onCancel: () => {
        console.log('取消')
        dialog.unmount()
      }
    }
  })
}
</script>

<template>
  <button @click="openDialog">打开弹窗</button>
</template>

这是 API 限制。但可以使用 Teleport 实现你想要的效果:

<template>
 <teleport defer to=".content-floating-box">
   <component :is="vnode" />
 </teleport>
</template>

然后可以把这玩意儿封装成一个组件,接受 props.to 和 props.vnode。基本上就能愉快玩耍了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题