最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to programmatically create a component instance in Vue 3? - Stack Overflow

programmeradmin2浏览0评论

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.

In Vue 2, I found this pattern:

// 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? You can see a full example of the DialogService.js by clicking here.

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.

In Vue 2, I found this pattern:

// 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? You can see a full example of the DialogService.js by clicking here.

Share Improve this question edited Oct 29, 2021 at 17:51 darkylmnx asked Oct 27, 2021 at 3:29 darkylmnxdarkylmnx 2,0715 gold badges24 silver badges39 bronze badges 4
  • Not a duplicate but related, stackoverflow.com/questions/63471824/vue-js-3-event-bus – Estus Flask Commented Oct 27, 2021 at 6:21
  • Actually not related at all. The event bus thing isn't the issue here. – darkylmnx Commented Oct 28, 2021 at 11:32
  • It is. You create event bus that can be used programmatically (on and emit methods). Vue 3 doesn't provide such bus, so it needs to be provided externally. The rest could be the same, more or less. 'new Vue' is replaced with 'createApp'. Think of it not as of extended comp, but as of sub-app, because it really is one – Estus Flask Commented Oct 28, 2021 at 14:55
  • 2 Well, createApp doesn't keep the context of the previous app, while Vue.extend did, so createApp isn't the solution anyway here. I changed the title so that it's more explicit. – darkylmnx Commented Oct 29, 2021 at 17:52
Add a comment  | 

4 Answers 4

Reset to default 10

Here's how to do with createApp in Vue 3, but the context (stores, plugins, directives...) will not be kept.

// 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.removeChild(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

I would recommend using the 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 })

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. It can be replaced with lightweight third-party alternatives like mitt or eventemitter3.

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. Modals and other screen UI elements are common use cases for it

<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. 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. In this case an instance of event bus that can be passed through the application can be used for interaction.

发布评论

评论列表(0)

  1. 暂无评论