【问题标题】:Is it possible to render a component automatically in vue 3?是否可以在 vue 3 中自动渲染组件?
【发布时间】:2021-02-27 21:52:15
【问题描述】:

我正在开发用于显示 vue 3 的通知/toast 的小型库。 我的想法是在插件注册期间为我的通知附加一个不可见的容器。所以最终用户不应该关心渲染这个区域。有可能吗?

我目前的插件是这样的:

export const plugin = {
    install: (app: App, options?) => {
        options = reactive(options || defaultOptions);

        app.provide(symbol, instance);
        app.component('vue3-notification', Notification);
        app.component('vue3-notifications', Overlay);
        console.log('app', app); // app._component is null at this point
        var test = Overlay.render({ notifications: instance });
        console.log('test', test); // how to attach Overlay component to app?
    }
};

似乎在安装插件时 vue 根容器尚不可用。我设法渲染我的组件以提供所需的依赖项(至少我希望如此,它已在最后一行记录到控制台)但我不知道如何安装它并与主应用程序集成。

我想从插件自动渲染的叠加组件如下所示:

<div class="notifications-overlay">
    <Teleport to="body">
        <vue3-notification
            v-for="(n, index) in notifications.stack.value"
            :key="n.id"
            v-bind="n"
            v-bind:hide="() => hide(n.id)"
        ></vue3-notification>
    </Teleport>
</div>

而且它有固定的位置:

.notifications-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
}

所以它在哪里渲染并不重要,我只想在使用我的插件后自动在 vue 应用程序中可用。

有什么想法吗?

【问题讨论】:

  • github.com/yariksav/vuetify-dialog 创建一个特定的 div 用于显示通知和祝酒词。查看他们的代码以了解他们是如何做到的,我们只需使用他们的包....

标签: vue.js vuejs3


【解决方案1】:

在 Vue 2 中,我们有 Vue.extend 用于创建基本 Vue 构造函数的“子类”,它还允许您将实例挂载到元素上,这非常适合此目的。

但是,this API has been removed in Vue 3。阅读the RFC for global API changes

由于全局 Vue 不再是可新建的构造函数,因此 Vue.extend 在构造函数扩展方面不再有意义。

好消息是,我们仍然可以通过利用 createApp 将我们的插件组件和 mount 渲染到 DOM 元素来实现几乎相同的目标。

如果您不喜欢实例化多个 Vue 实例的想法,您可能需要查看这个名为 mount-vue-component 的非官方库。我自己没有尝试过,但它允许您在不使用createApp 的情况下挂载组件。虽然,它似乎使用了一些内部属性(如_context)来完成任务。我会说任何没有记录的东西都可能会改变。但是,嘿。

所以,回到createApp 方法。我们不会在这里使用Teleport。以下步骤只是我的偏好,因此请根据您的用例随意调整。

添加接口

import { ComponentPublicInstance } from 'vue';

export interface INotify {
  (message: string): void;
}

export type CustomComponentPublicInstance = ComponentPublicInstance & {
  notify: INotify;
}

我们正在为我们的自定义组件实例使用交集类型。

插件实现

import { App, createApp } from 'vue';
import Notifier from './path/to/component/Notifier.vue';

export const injectionKeyNotifier = Symbol('notifier');

export default {
  install(app: App) {
    const mountPoint = document.createElement('div');
    document.body.appendChild(mountPoint);

    const notifier = createApp(Notifier).mount(mountPoint) as CustomComponentPublicInstance;

    app.provide(injectionKeyNotifier, notifier.notify);
  }
}

此时,我们只需要从目标组件(Notifier.vue)公开一个公共方法(参见上面的INotify)。我称这个方法为notify。它接受消息的字符串参数。

组件:Notify.vue

<template>
  <div class="my-notifier">
    <div class="msg" v-text="message"></div>
  </div>
</template>

<script lang="ts">
  import { defineComponent, ref } from 'vue';

  export default defineComponent(() => {
    const message = ref('');

    function notify(msg: string) {
      message.value = msg;
    }

    return {
      message,
      notify
    }
  })
</script>

注意一个名为notify 的命名函数。这是我们之前讨论过的公共方法,我们需要将其导出。

现在use()

在您的条目文件上(例如main.ts):

import { createApp } from 'vue'
import App from './App.vue'
import Notifier from 'my-custom-notifier'; // Assumes a library from the NPM registry (if you mean to publish it)

createApp(App)
  .use(Notifier)
  .mount('#app');

显示随机通知的示例用法:

<template>
  <div class="home">
    <button @click="showNotifs">Show notification</button>
  </div>
</template>

<script lang="ts">
  import { defineComponent, inject } from 'vue';
  import { INotify, injectionKeyNotifier } from 'my-custom-notifier';

  export default defineComponent({
    name: 'Home',

    setup() {
      const notify = inject(injectionKeyNotifier) as INotify;

      function showNotifs() {
        notify('You have x unread messages.');
      }

      return {
        showNotifs
      }
    }
  })
</script>

就是这样!我们会自动注册组件,而无需用户手动将其添加到模板中。

【讨论】: