简体   繁体   English

如何以编程方式在 Vue 3 中创建组件实例?

[英]How to programmatically create a component instance in Vue 3?

I have a Vue 2 pattern I was using for a common scenario: programmatically creating an instance to open a Modal/Dialog/Lightbox on dynamic content outside of a template.我有一个用于常见场景的 Vue 2 模式:以编程方式创建一个实例以在模板外部的动态内容上打开模式/对话框/灯箱。

In Vue 2, I found this pattern:在 Vue 2 中,我发现了这种模式:

// DialogService.js

export default {
  alert(text) {
    const DialogClass = Vue.extend(DialogComponentDef);
    let dialog = new DialogClass({ propsData: { text } });

    dialog.$on('close', () => {
      dialog.$destroy();
      dialog.$el.remove();
      dialog = null;
    });

    // mount the dynamic dialog component in the page
    const mountEl = document.createElement('div');
    document.body.appendChild(mountEl);
    dialog.$mount(mountEl);
  },
};

How can I acheive this in Vue 3, knowing Vue.extends , $on & $destroy do not exist anymore?知道Vue.extends$on$destroy不再存在,我如何在 Vue 3 中实现这一点? You can see a full example of the DialogService.js by clicking here .您可以单击此处查看DialogService.js 的完整示例。

Here is the simple way to call and run a component programmatically这是以编程方式调用和运行组件的简单方法

/* DialogService.js */
import DialogVue from './Dialog.vue';
import { createApp } from 'vue';

const Dialog = (options = {}) => {
  const onClose = options.onClose;
  const tempDiv = document.createElement('div');
  const instance = createApp(DialogVue).mount(tempDiv);

  instance.title = options.title;
  instance.text = options.text;
  instance.onClose = options.onClose;
  instance.show = true;

  document.body.appendChild(instance.$el);
}

export default Dialog;
<!-- Dialog.vue -->
<template>
  <transition name="fade-bottom">
    <h3 v-if="title">{{ title }}</h3>
    {{ text }}
    <button @click="show = false; onClose()">Cancel</button>
  </transition>
</template>

<script setup>
import { ref, defineExpose } from 'vue'

const show = ref(false)
const title = ref('')
const text = ref('')
const onClose = () => {}

defineExpose({
  title,
  text,
  show,
  onClose
})

</script>

Vue 3 doesn't provide a generic event bus. Vue 3 不提供通用的事件总线。 It can be replaced with lightweight third-party alternatives like mitt or eventemitter3 .它可与轻量级第三方的替代品等代替mitteventemitter3

A component can be mounted outside application element hierarchy with a teleport .组件可以通过传送安装在应用程序元素层次结构之外。 This has been previously available in Vue 2 with third-party portal-vue library.这之前在 Vue 2 中已经提供,带有第三方portal-vue库。 Modals and other screen UI elements are common use cases for it模态和其他屏幕 UI 元素是它的常见用例

<teleport to="body">
  <DialogComponent ref="dialog" @close="console.log('just a notification')">
   Some markup that cannot be easily passed as dialog.value.show('text')
  </DialogComponent>
</teleport>

Where DialogComponent controls its own visibility and doesn't need to be explicitly unmounted like in original snippet. DialogComponent控制自己的可见性,不需要像原始代码段那样显式卸载。 A cleanup is performed automatically on parent unmount:在父卸载时自动执行清理:

<teleport to="body">
  <div v-if="dialogState">
    <slot>{{dialogText}}</slot>
  </div>
</teleport>

and

let dialogState = ref(false);
let dialogText = ref('');
let show = (text) => {
  dialogText.value = text;
  dialogState.value = true;
} ;
...
return { show };

For more complex scenarios that require to manage multiple instances, or access show outside components in business logic, a teleport needs to be mounted at the top of component hierarchy.对于需要管理多个实例或访问show业务逻辑中的外部组件的更复杂的场景,需要在组件层次结构的顶部安装传送。 In this case an instance of event bus that can be passed through the application can be used for interaction.在这种情况下,可以通过应用程序传递的事件总线实例可用于交互。

Here's how to do with createApp in Vue 3, but the context (stores, plugins, directives...) will not be kept.下面是在 Vue 3 中如何使用createApp ,但是上下文(商店、插件、指令...)不会被保留。

// DialogService.js
import { createApp } from 'vue';

export default {
  alert(text) {
    const mountEl = document.createElement('div');
    document.body.appendChild(mountEl);

    const dialog = createApp({ extends: DialogComponentDef }, {
      // props
      text,
      // events are passed as props here with on[EventName]
      onClose() {
        mountEl.parentNode.remvoeChild(mountEl);
        dialog.unmount();
        dialog = null;
      },
    });

    dialog.mount(mountEl);
  },
};

To keep the context, there's something more complicated that can be seen here with h and render Vue methods : https://github.com/vuejs/vue-next/issues/2097#issuecomment-709860132为了保持上下文,可以在这里使用hrender Vue 方法看到更复杂的东西: https : //github.com/vuejs/vue-next/issues/2097#issuecomment-709860132

I would recommend using the mount-vue-component .我建议使用mount-vue-component It's lightweight and easy to use.它重量轻且易于使用。 Code example:代码示例:

import MyComponent1 from './MyComponent1.vue'
import MyComponent2 from './MyComponent2.vue'
import { mount } from 'mount-vue-component'

let dynamicComponent = mount(someCondition ? MyComponent1 : MyComponent2, { props: { <someProperties...> }, app: MyVueApp })

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM