如何通过编程方式渲染Vuejs 3中的组件?

发布于 2025-01-17 21:47:24 字数 5490 浏览 3 评论 0原文

我正在使用Vuejs 3。我正在尝试创建一个可以在整个应用程序中注入各种组件的对话框服务,然后用来打开对话框。例如:

使用构图API的理想状态:

rando_component.js

<template>
    <div class="header-controls-layout">
        <div class="left">
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                Media Library
            </h2>
        </div>
        <div class="right">
            <button @click="openUploadModal()">Upload</button>
        </div>
    </div>
</template>

<script setup>
import UploadMediaModal from '@/Components/UploadMediaModal.vue'

const dialogService = inject('dialog');// Inject dialog service

function updateTileSize(size){
    this.tileSize = size;
}

function openUploadModal(){
   //Open a dialog and pass a component to render with optional settings
   dialogService.open(UploadMediaModal, {data: {}, width: '800px', height: '80vh'});
}

</script>

您会注意到,我正在注入键'对话框。在安装应用程序之前,我将在app.js中注册。

app.js

const vueapp = createApp();
        
VueSingleton.prototype.$vue = vueapp;
const instance = new DialogService("dialog-container");
vueapp.provide('dialog', instance);

vueapp.use(plugin);
vueapp.mixin({ methods: { route } });
vueapp.component('Dialog', Dialog);
vueapp.mount('#app');

dialog.service.js

import { VueSingleton } from "./singleton.vue";
import { ref, onUnmounted, createVNode, render } from 'vue';

export class DialogService extends VueSingleton {
    _container = null;
    _container_selector = '';
    _modals = [];
    

    constructor(selector){
        super();
        this._container_selector = selector;
    }

    open(component, {data, width, height, maxWidth, maxHeight}){

        this._container = window.document.getElementById(this._container_selector);

        let appContext = this.$vue._context.app._instance.appContext; // Seems wrong to me
        let vnode = createVNode('Dialog', {component, data, width, height, maxWidth, maxHeight});

        vnode.appContext = {...appContext };
        render(vnode, this._container);    
    }
}

singleton.vue.js

export class VueSingleton{}

到目前为止,一切或多或少地工作。

rando_component.js上方的方法中启动openuploadmodal方法,将对话框组件注入DOM。我可以在文档树检查器中看到对话框组件。

但是,这是问题开始的地方。如您所见,在上面的rando_component.js中,我将一个组件(uploadMediamodal)传递给OPEN方法。

这里的目标是,我想在对话框组件中渲染此组件(uploadMediamodal)。,但是对话框组件并没有呈现其模板,甚至没有触发其生命周期挂钩。

为什么对话框组件渲染不?此外,如何通过编程方式渲染对话框组件中的另一个组件?

这是对话框组件: 对话框

<script setup>
    import { ref, onUnmounted, getCurrentInstance,h, createVNode, render, onMounted } from 'vue';

    const props = defineProps(["component", "data", "width", "max-width", "height", "max-height"]);
    const container = ref();
    const { appContext } = getCurrentInstance();
    
    onMounted(() => {
        // Not Firing
        renderDialog();
    })

    // Destory Comp on unmount
    onUnmounted(() => {
        render(null, container);
    });

    function renderDialog(){
        
        let vnode = createVNode(props.component, props.data || {});
        vnode.appContext = {...appContext };
        render(vnode, container);
    }
</script>

<template>
  <transition name="modal-fade">
    <div class="modal-backdrop">
      <div class="modal" role="dialog" aria-labelledby="modalTitle" aria-describedby="modalContent" :style="{'width': props.width || '600px', 'max-width': props.maxWidth || '100%', 'height': props.height || '400px', 'max-height': props.maxHeight || '80vh'}">
        <!-- Modal Injecttion Container -->
        <div ref="container" class="modal-body" id="modalContent"></div>
      </div>
    </div>
  </transition>
</template>

<style>
  .modal-backdrop {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: rgba(0, 0, 0, 0.3);
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .modal {
    background: #FFFFFF;
    box-shadow: 2px 2px 20px 1px;
    overflow-x: auto;
    display: flex;
    flex-direction: column;
  }


  .modal-body {
    position: relative;
    padding: 20px 10px;
  }

  .btn-close {
    position: absolute;
    top: 0;
    right: 0;
    border: none;
    font-size: 20px;
    padding: 10px;
    cursor: pointer;
    font-weight: bold;
    color: #4AAE9B;
    background: transparent;
  }

  .btn-green {
    color: white;
    background: #4AAE9B;
    border: 1px solid #4AAE9B;
    border-radius: 2px;
  }

  .modal-fade-enter,
  .modal-fade-leave-to {
    opacity: 0;
  }

  .modal-fade-enter-active,
  .modal-fade-leave-active {
    transition: opacity .5s ease;
  }
</style>

<script setup>

</script>

<template>
    <p>Upload Media Modal component working!</p>
</template>

<style lang="scss" scoped>

</style>

I'm using VueJS 3. I'm trying to create a dialog service that can be injected into various components throughout the application and then used to open a dialog. For example:

Ideal State using composition API:

random_component.js

<template>
    <div class="header-controls-layout">
        <div class="left">
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                Media Library
            </h2>
        </div>
        <div class="right">
            <button @click="openUploadModal()">Upload</button>
        </div>
    </div>
</template>

<script setup>
import UploadMediaModal from '@/Components/UploadMediaModal.vue'

const dialogService = inject('dialog');// Inject dialog service

function updateTileSize(size){
    this.tileSize = size;
}

function openUploadModal(){
   //Open a dialog and pass a component to render with optional settings
   dialogService.open(UploadMediaModal, {data: {}, width: '800px', height: '80vh'});
}

</script>

As you'll notice, I'm injecting the key 'dialog'. I'm registering this in app.js just before mount the application.

app.js

const vueapp = createApp();
        
VueSingleton.prototype.$vue = vueapp;
const instance = new DialogService("dialog-container");
vueapp.provide('dialog', instance);

vueapp.use(plugin);
vueapp.mixin({ methods: { route } });
vueapp.component('Dialog', Dialog);
vueapp.mount('#app');

dialog.service.js

import { VueSingleton } from "./singleton.vue";
import { ref, onUnmounted, createVNode, render } from 'vue';

export class DialogService extends VueSingleton {
    _container = null;
    _container_selector = '';
    _modals = [];
    

    constructor(selector){
        super();
        this._container_selector = selector;
    }

    open(component, {data, width, height, maxWidth, maxHeight}){

        this._container = window.document.getElementById(this._container_selector);

        let appContext = this.$vue._context.app._instance.appContext; // Seems wrong to me
        let vnode = createVNode('Dialog', {component, data, width, height, maxWidth, maxHeight});

        vnode.appContext = {...appContext };
        render(vnode, this._container);    
    }
}

singleton.vue.js

export class VueSingleton{}

Up until this point, everything works as expected more or less.

Firing the openUploadModal method in method in the random_component.js up above, injects the dialog component into the DOM. I can see the dialog component in the document tree inspector.

However this is where the problem begins. As you can see, in the random_component.js above I'm passing a component(UploadMediaModal) to the open method.

The goal here is that I would like to render this component(UploadMediaModal) in the dialog component. But the Dialog component isn't rendering it's template or even firing it's lifecycle hooks.

Why isn't the dialog component rendering? Additionally, how do I programmatically render another component inside the dialog component?

Here is the dialog component:
Dialog.vue

<script setup>
    import { ref, onUnmounted, getCurrentInstance,h, createVNode, render, onMounted } from 'vue';

    const props = defineProps(["component", "data", "width", "max-width", "height", "max-height"]);
    const container = ref();
    const { appContext } = getCurrentInstance();
    
    onMounted(() => {
        // Not Firing
        renderDialog();
    })

    // Destory Comp on unmount
    onUnmounted(() => {
        render(null, container);
    });

    function renderDialog(){
        
        let vnode = createVNode(props.component, props.data || {});
        vnode.appContext = {...appContext };
        render(vnode, container);
    }
</script>

<template>
  <transition name="modal-fade">
    <div class="modal-backdrop">
      <div class="modal" role="dialog" aria-labelledby="modalTitle" aria-describedby="modalContent" :style="{'width': props.width || '600px', 'max-width': props.maxWidth || '100%', 'height': props.height || '400px', 'max-height': props.maxHeight || '80vh'}">
        <!-- Modal Injecttion Container -->
        <div ref="container" class="modal-body" id="modalContent"></div>
      </div>
    </div>
  </transition>
</template>

<style>
  .modal-backdrop {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: rgba(0, 0, 0, 0.3);
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .modal {
    background: #FFFFFF;
    box-shadow: 2px 2px 20px 1px;
    overflow-x: auto;
    display: flex;
    flex-direction: column;
  }


  .modal-body {
    position: relative;
    padding: 20px 10px;
  }

  .btn-close {
    position: absolute;
    top: 0;
    right: 0;
    border: none;
    font-size: 20px;
    padding: 10px;
    cursor: pointer;
    font-weight: bold;
    color: #4AAE9B;
    background: transparent;
  }

  .btn-green {
    color: white;
    background: #4AAE9B;
    border: 1px solid #4AAE9B;
    border-radius: 2px;
  }

  .modal-fade-enter,
  .modal-fade-leave-to {
    opacity: 0;
  }

  .modal-fade-enter-active,
  .modal-fade-leave-active {
    transition: opacity .5s ease;
  }
</style>

UploadMediaModal.vue

<script setup>

</script>

<template>
    <p>Upload Media Modal component working!</p>
</template>

<style lang="scss" scoped>

</style>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文