vue 3 可以自动渲染组件吗?

Rom*_*ada 4 vue.js vuejs3

我正在开发一个小型库,用于显示 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?
    }
};
Run Code Online (Sandbox Code Playgroud)

似乎安装插件后,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>
Run Code Online (Sandbox Code Playgroud)

并且它有固定的位置:

.notifications-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
}
Run Code Online (Sandbox Code Playgroud)

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

有什么想法吗?

Yom*_* S. 7

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

然而,这个 API 已经在 Vue 3 中被删除了。阅读RFC 以了解全局 API 更改

由于全局 Vue 不再是可 new 的构造函数,因此 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;
}
Run Code Online (Sandbox Code Playgroud)

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

插件实现

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

此时,我们只需要从INotify目标组件 ( Notifier.vue) 公开一个公共方法(见上文)。我正在调用这个方法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>
Run Code Online (Sandbox Code Playgroud)

请注意一个名为 的命名函数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');
Run Code Online (Sandbox Code Playgroud)

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

<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>
Run Code Online (Sandbox Code Playgroud)

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