为naive-ui添加全局的message函数
前言 🔗
为 naive-ui 添加全局的 message ,notification , loaddingBar , dialog 函数
对于 Vue3 ,个人非常喜欢 naive-ui 这个 ui 库,并且也在工作中把它应用到了相应的项目之中
虽然它看起来有点像 ant-design , 不过国内的 ui 库大体都是以蓝色为主色,看起来就有些审美疲劳了
正文 🔗
我们都知道,像 element-ui , ant-design 等都有一些通用的方法可供全局使用,即可以脱离组件的上下文
比如 element-plus 的
我们可以在任何地方使用这些组件,比如 axios 的拦截器中,或者路由守卫( hooks )中
而在 naive-ui 中,使用 message 的方法比较特别
首先是必须包在 n-message-provider 组件下
然后使用 useMessage 来获取 message 实例
App.vue
<script setup lang="ts">
import { NMessageProvider } from 'naive-ui';
import Content from './Content.vue';
</script>
<template>
<NMessageProvider>
<Content></Content>
</NMessageProvider>
</template>Content.vue
<script setup lang="ts">
import { useMessage } from 'naive-ui';
const message = useMessage();
const open = () => {
message.info('我是消息')
}
</script>
<template>
<button @click="open">打开message</button>
</template>看起来有点复杂
我去翻了下历史的 issues 记录, 发现有人已经有提过相关的 issue 了
不过作者似乎并不想提供这样的 api ,作者在第三个 issue 中回复了
If you must need to render a message before app is ready, you need to render a app outside current app and set message api globally. However remember message is a part of your app. You can’t operate a phone before you turn it on.
意思是如果确实需要在 app 被挂在前调用 message, 那么需要在原 app 外部额外渲染一个 app ,并且把相关的 api 全局化
作者的意思很简单,想用 message, 就是得 app 挂载了才能用,因为 message 就是整个 app 的一个部分
作者还举了个例子:你不能在手机还没开机的时候就操作它
所以,我们需要按照作者说的来进行 hack
干掉 useXXX 🔗
每次都要 useMessage 很麻烦,那么如何才能导出一个全局变量呢?
可以通过添加一个空的组件来把 message 提升到全局
MessageInjectWindow.vue
<script setup lang="ts">
import { useMessage } from "naive-ui";
window["$message"] = useMessage();
</script>
<template></template>然后我们把上面的组件放到 App.vue 中
<script setup lang="ts">
import { NMessageProvider } from 'naive-ui';
import Content from './Content.vue';
</script>
<template>
<NMessageProvider>
<MessageInjectWindow></MessageInjectWindow>
<Content></Content>
</NMessageProvider>
</template>然后在 Content.vue 中就可以使用 $message 了
<script setup lang="ts">
const open = () => {
$message.info('我是消息');
}
</script>
<template>
<button @click="open">打开message</button>
</template>不过在代码的过程中总觉得缺了点啥?没错,就是代码提示
这是一个 ts 的项目,通过添加 d.ts 可以为 $message 赋予类型提示
创建 global.d.ts 然后输入以下内容
declare global {
// 可以直接使用 $message
const $message: import("naive-ui").MessageApi;
const $dialog: import("naive-ui").DialogApi;
const $notification: import("naive-ui").NotificationApi;
const $loadingBar: import("naive-ui").LoadingBarApi;
interface Window {
// 挂载需要的类型提示
// 或者通过 window.$message 使用
$message: import("naive-ui").MessageApi;
$dialog: import("naive-ui").DialogApi;
$notification: import("naive-ui").NotificationApi;
$loadingBar: import("naive-ui").LoadingBarApi;
}
}
export {};在 Window 下定义是因为需要通过 window['$xxx'] = useXXX() 来挂载, 不加的话在 ts 项目下就会有红线
在 global 下定义意味着除了 window['$xxx'] 来调用之外,也可以直接使用 $xxx 直接使用
这样子代码就有不错的提示了
干掉只能在app内使用 🔗
虽然我们已经提取了全局的 api 了,但是依然无法在没有 app 的上下文下使用
比如如果我想在 mount 之前调用一个接口,这个接口要使用 message 来显示一些信息,那么就会报错
import { createApp } from "vue";
import App from "./App.vue";
const bootstrap = async () => {
// 想在挂在前调用一个接口
await Promise.resolve().then(() => {
$message.info("hello");
});
// 接口完成再挂载 app
createApp(App).mount("#app");
};
bootstrap();结果显而易见,报错
原因很简单,都没挂载 app ,那么 MessageInjectWindow.vue 的 setup 不会执行,自然也就没有注入 message 实例了
那么这时候就需要构建一个空的 app ,在目标 app 之前挂载,然后全局化 api
创建 AppProvider.vue
<script lang="ts" setup>
import { NMessageProvider } from "naive-ui";
import MessageInjectWindow from "./MessageInjectWindow.vue";
</script>
<template>
<NMessageProvider>
<MessageInjectWindow></MessageInjectWindow>
</NMessageProvider>
</template>内容基本一样,不过没有 Content.vue ,因为作为一个空的 app ,不需要渲染真正的内容节点
然后修改 main.ts
import { createApp } from "vue";
import App from "./App.vue";
import AppProvider from './AppProvider.vue';
const bootstrap = async () => {
// 挂载一个空的 app
createApp(AppProvider).mount('#app-provider');
// 想在挂在前调用一个接口
await Promise.resolve().then(() => {
$message.info("hello");
});
// 接口完成再挂载 app
createApp(App).mount("#app");
};
bootstrap();然后就可以愉快的使用全局的 $message 了
后记 🔗
这个方法是从 Naive-ui-admin 以及相关的 issues 查找到的
目前已经用在公司的项目中
如果只是讨厌每次调用接口时写 useXXX
issues 中也有用户提议可以封装通用的 useRequest
反正,萝卜青菜各有所爱吧~


