Naive UI Message 信息提示组件完全指南:从 useMessage 到 setup 外调用
【免费下载链接】naive-uiA Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.项目地址: https://gitcode.com/gh_mirrors/na/naive-ui
导读
Message 是 Naive UI 中最常用的轻量级反馈组件——它"(一般是)从浏览器顶部降下来的神谕",用于在页面顶部(或底部)短暂展示操作结果、错误警告等信息。本文以 src/message/demos/zhCN/index.demo-entry.md 为骨架,结合 MessageProvider.tsx、Message.tsx、MessageEnvironment.tsx 等源码实现,完整讲解 MessageProvider 的全部 Props、useMessage 注入 API、MessageOption 与 MessageReactive 的类型细节,以及"在 setup 外使用 Message"的两种官方方案,帮助你写出可投入生产的消息提示代码。
使用前提:组件必须处于 n-message-provider 内部
Message 与普通组件不同,它不通过模板标签直接渲染,而是通过命令式 API(useMessage())调用。这意味着调用useMessage的组件必须位于n-message-provider组件内部,否则会抛出错误。官方文档给出如下示例:
<!-- App.vue --> <n-message-provider> <content /> </n-message-provider>import { useMessage } from 'naive-ui' import { defineComponent } from 'vue' // content export default defineComponent({ setup() { const message = useMessage() return { warning() { message.warning('...') } } } })从源码看,这一约束是有严格保证的。use-message.ts 的实现非常直白:它通过 Vue 的inject从注入链中取出messageApiInjectionKey,如果取不到(即外层没有n-message-provider),会直接调用throwError抛出错误:
export function useMessage(): MessageApiInjection { const api = inject(messageApiInjectionKey, null) if (api === null) { throwError( 'use-message', 'No outer <n-message-provider /> founded. See prerequisite in ...' ) } return api }而注入的源头在 MessageProvider.tsx:setup阶段通过provide(messageApiInjectionKey, api)把完整的 API 对象提供给后代组件,同时provide(messageProviderInjectionKey, { props, mergedClsPrefixRef })供内部 Message 读取 Provider 的配置与主题前缀。这就是"Provider 必须包裹使用方"这一规则的底层原因——API 实例本身就是通过 Vue 依赖注入机制传递的。
演示速览
官方文档共提供 12 个演示,覆盖了 Message 的核心能力:
| 演示文件 | 讲解内容 |
|---|---|
| basic.demo.vue | 基础用法:info / error / warning / success / loading 五种类型 |
| icon.demo.vue | 通过icon选项自定义图标(如沙漏图标) |
| timing.demo.vue | 用duration设定持续时间(如 5 秒) |
| closable.demo.vue | 设定closable使 Message 可点击关闭 |
| modify-content.demo.vue | 通过返回的 MessageReactive 实时修改内容与类型 |
| manually-close.demo.vue | 通过destroy()手动关闭 |
| about-theme.demo.vue | 主题随 Provider 联动,展示期间可切换主题 |
| multiple-line.demo.vue | 多行文本展示 |
| placement.demo.vue | 六种弹出位置切换 |
| customize-message.demo.vue | 用render自定义渲染(如用 Alert 充当 Message) |
| no-icon.demo.vue | 用showIcon: false隐藏图标 |
| rtl-debug.demo.vue | RTL(从右到左)布局调试 |
其中值得注意的两个"进阶"演示将在后文展开:modify-content揭示了 MessageReactive 是一个响应式对象,customize-message揭示了render完全接管渲染的能力。
MessageProvider Props:全局配置所有消息
在应用根组件挂载n-message-provider时,可通过以下 Props 对所有消息进行全局默认配置:
| 名称 | 类型 | 默认值 | 说明 | 版本 |
|---|---|---|---|---|
| closable | boolean | false | 所有 Message 是否显示 close 图标 | |
| container-class | string | undefined | Message 容器的类名 | 2.36.0 |
| container-style | string \| CSSProperties | undefined | Message 容器的样式 | |
| duration | number | 3000 | 所有 Message 默认的持续时长(毫秒) | |
| keep-alive-on-hover | boolean | false | 所有 Message 在悬浮时是否不销毁 | |
| max | number | undefined | 限制同时显示的提示信息个数 | |
| placement | top \| top-left \| top-right \| bottom \| bottom-left \| bottom-right | top | 所有 Message 显示的位置 | |
| to | string \| HTMLElement | 'body' | Message 容器节点的挂载位置 |
这些 Props 的默认值可以在 message-props.ts 的messageProviderProps定义(位于 MessageProvider.tsx)中得到源码级印证:duration默认3000,placement默认'top',to接受string | HTMLElement,containerStyle同时接受字符串与对象。
结合源码可以更深入理解几个关键 Props 的底层行为:
to与 Teleport:在 MessageProvider.tsx 的render中,所有消息被包裹进<Teleport to={this.to ?? 'body'}>。默认挂载到body,因此消息能浮现在页面最顶层;你也可以把它传到一个指定的 DOM 节点或选择器字符串上,例如弹层内部。placement决定容器类名与对齐方式:渲染时容器类名为${mergedClsPrefix}-message-container--${this.placement};同时 Message.tsx 会根据 placement 是否以top开头设置alignItems: 'flex-start'或'flex-end',实现六种位置的对齐。max的淘汰策略:在create内部(MessageProvider.tsx),当已有消息数量达到max时,会先shift()移除列表头部(最早的一条),再 push 新消息——即"新消息挤掉最旧消息"的先进先出策略。closable/duration/keepAliveOnHover的逐条覆盖:Provider 渲染每个MessageEnvironment时,会判断单条消息是否显式传入了对应选项(MessageProvider.tsx),未传入的才回落到 Provider 的全局值,实现"全局默认 + 单条覆盖"。
另外需要说明container-class与container-style:它们作用于整个消息容器(即包裹所有消息的外层div,见 MessageProvider.tsx),可用于调整容器的层级、宽度等整体表现,适合在需要精确定位容器场景下使用。
useMessage 注入 API:七种命令式方法
useMessage()返回的 API 对象类型为MessageApiInjection(即公开的MessageApi),在 MessageProvider.tsx 中定义,包含以下方法:
| 名称 | 类型 | 说明 | 版本 |
|---|---|---|---|
| destroyAll | () => void | 销毁所有弹出的信息 | |
| create | (content: string \| (() => VNodeChild), option?: MessageOption) => MessageReactive | 创建自定义类型的信息 | 2.25.7 |
| error | (content: string \| (() => VNodeChild), option?: MessageOption) => MessageReactive | 创建 error 类型的信息 | |
| info | (content: string \| (() => VNodeChild), option?: MessageOption) => MessageReactive | 创建 info 类型的信息 | |
| loading | (content: string \| (() => VNodeChild), option?: MessageOption) => MessageReactive | 创建 loading 类型的信息 | |
| success | (content: string \| (() => VNodeChild), option?: MessageOption) => MessageReactive | 创建 success 类型的信息 | |
| warning | (content: string \| (() => VNodeChild), option?: MessageOption) => MessageReactive | 创建 warning 类型的信息 |
从源码实现看(MessageProvider.tsx),info/success/warning/error/loading都是对内部create的封装——它们在调用时自动注入对应的type字段:
const api: MessageApiInjection = { create(content, options) { return create(content, { type: 'default', ...options }) }, info(content, options) { return create(content, { ...options, type: 'info' }) }, // success / warning / error / loading 同理 destroyAll }content参数既可以是普通字符串,也可以是返回VNodeChild的函数,后者在 Message.tsx 中通过render(content)渲染——这为"富文本/插值内容"提供了可能。每个方法都返回一个MessageReactive响应式对象,且所有方法都接受可选的MessageOption作为第二参数。
MessageOption:单条消息的配置选项
调用message.info('...', option)时,第二参数option的类型为MessageOption,其完整定义在 types.ts:
| 名称 | 类型 | 说明 | 版本 |
|---|---|---|---|
| closable | boolean | 是否显示 close 图标 | |
| duration | number | 信息展示的时长(毫秒) | |
| icon | () => VNodeChild | 信息图标 | |
| keepAliveOnHover | boolean | Hover 到信息上是否不销毁 | |
| render | MessageRenderMessage | 消息的渲染函数 | 2.24.0 |
| showIcon | boolean | 是否展示图标 | 2.25.7 |
| spinProps | { strokeWidth?: number, stroke?: string, scale?: number, radius?: number } | 加载图标的属性 | 2.44.0 |
| type | 'info' \| 'success' \| 'warning' \| 'error' \| 'loading' \| 'default' | 信息类型 | 默认'default'(2.25.7) |
| onAfterLeave | () => void | 信息消失动画结束的回调 | |
| onClose | () => void | 点击关闭图标的回调 | |
| onLeave | () => void | 信息开始消失的回调 |
结合源码,几个选项的底层行为如下:
icon与内置图标映射:Message.tsx 维护了一张iconRenderMap,把info/success/warning/error映射到_internal/icons中的内置图标,default类型则返回null(无图标)。createIconVNode函数(Message.tsx)的逻辑是:若显式传入icon函数则优先使用它;否则loading类型渲染NBaseLoading旋转加载图标,其余类型从映射表取默认图标。loading与spinProps:loading 图标本质是NBaseLoading组件,spinProps直接透传给该组件(源码中默认strokeWidth={24}、scale={0.85},用户传入的属性会覆盖默认值),用于微调加载图标的粗细、颜色与缩放。showIcon默认值为true(见 message-props.ts),设false可隐藏图标,参见 no-icon.demo.vue 的用法:message.warning('...', { showIcon: false })。render完全自定义渲染:传入render后,默认的消息外壳(图标 + 内容 + 关闭按钮)将不再使用,而是调用renderMessage(this.$props)直接输出你的 VNode。官方演示 customize-message.demo.vue 用NAlert充当消息体,并通过'var(--n-box-shadow)'沿用主题变量,实现"换个组件当 Message"的效果。
MessageRenderMessage 类型
当使用render选项时,回调参数类型定义如下:
type MessageRenderMessage = (props: { content?: string | number | (() => VNodeChild) icon?: () => VNodeChild closable: boolean type: 'info' | 'success' | 'warning' | 'error' | 'loading' onClose?: () => void }) => VNodeChild其源码定义在 types.ts,是从MessageSetupProps中挑选出的closable | content | icon | onClose | type五个字段。也就是说,自定义渲染函数可以拿到单条消息的内容、类型、是否可关闭与关闭回调,方便你把这些信息映射到任意组件上(如NAlert的type、closable、onClose、default插槽)。
MessageReactive:可实时修改的响应式消息
create等方法的返回值是MessageReactive——它是一个Vue reactive 响应式对象,因此可以在消息存活期间动态修改其属性,界面会随之更新。官方文档定义的属性与方法如下:
MessageReactive Properties
| 名称 | 类型 | 说明 | 版本 |
|---|---|---|---|
| closable | boolean | 是否显示 close 图标 | |
| content | string \| (() => VNodeChild) | 信息内容 | |
| destroy | () => void | 销毁信息的方法 | |
| icon | () => VNodeChild | 信息图标 | |
| keepAliveOnHover | boolean | Hover 到信息上是否不销毁 | |
| showIcon | boolean | 是否展示图标 | 2.25.7 |
| type | 'info' \| 'success' \| 'warning' \| 'error' \| 'loading' \| 'default' | 信息类型 | 默认'default'(2.25.7) |
| onAfterLeave | () => void | 信息消失动画结束的回调 | |
| onLeave | () => void | 信息开始消失的回调 |
MessageReactive Methods
| 名称 | 类型 | 说明 |
|---|---|---|
| destroy | () | 销毁信息的方法 |
源码层面的关键点(MessageProvider.tsx):
- 每条消息在创建时会被赋予一个通过
createId()生成的唯一key,存入messageListRef响应式数组; - 返回给调用方的
messageReactive由reactive({ ...options, content, key, destroy })构造,其中destroy的实现是:找到该 key 对应的MessageEnvironment实例并调用其hide()方法,随后由handleAfterLeave(在离开动画结束后)把它从列表中移除; - 因为对象本身是响应式的,修改
msgReactive.content、msgReactive.type等字段会实时反映到界面上——这正是 modify-content.demo.vue 中"加一 / 改变类型"按钮背后的机制。
手动关闭的两种方式
- 通过返回的 MessageReactive:
const msg = message.info('...'); msg.destroy()。官方演示 manually-close.demo.vue 还展示了两个实用细节:设置duration: 0让消息不自动消失,以及组件卸载时在onBeforeUnmount中调用removeMessage()清理残留消息,避免内存泄漏。 - 通过
destroyAll():一次性销毁所有弹出的消息,适合在路由切换或登出等场景下清理界面。
位置、时长与悬浮:Message 的三大运行机制
六种弹出位置
通过placement可在top、top-left、top-right、bottom、bottom-left、bottom-right之间选择(placement.demo.vue 演示了通过n-message-provider :placement="placement"动态切换)。这一机制由容器类名 + Flex 对齐共同实现:容器使用不同的--{placement}修饰类定位消息组,而 Message.tsx 根据是否top开头设置 wrapper 的对齐方式,从而把单条消息锚定在容器内对应方位。
时长与定时器
MessageEnvironment(MessageEnvironment.tsx)在onMounted后调用setHideTimeout(),用window.setTimeout(hide, duration)实现自动消失;若duration为 0 或假值则不会设置定时器(这就是手动关闭演示中duration: 0能让消息常驻的原因)。
Hover 保持(keepAliveOnHover)
当开启keepAliveOnHover时,MessageEnvironment.tsx 会给消息挂上mouseenter/mouseleave监听:进入时clearTimeout暂停倒计时,离开时重新setHideTimeout()恢复倒计时。注意源码中if (e.currentTarget !== e.target) return的守卫,意味着该行为只对直接悬浮在消息本体上的事件生效。未开启该选项时,MessageEnvironment.tsx 不绑定任何悬浮监听,duration一到即消失。同时,Message的隐藏/显示被包裹在NFadeInExpandTransition过渡动画中(MessageEnvironment.tsx),onLeave/onAfterLeave回调分别对应动画开始与结束的时刻。
主题联动说明
Message 的主题遵循"就近继承"原则:如果你不明确指明主题,被创建信息的主题会与对应n-message-provider的主题一致(参见 about-theme.demo.vue,该演示允许在消息展示期间切换主题)。实现上,Message.tsx 通过inject(messageProviderInjectionKey)拿到 Provider 的props,再调用useTheme('Message', ...)解析主题变量,并通过cssVarsRef输出为--n-*系列 CSS 变量(如--n-color、--n-box-shadow、--n-text-color、--n-border-radius等,见 Message.tsx)。这意味着 Message 支持完整的主题定制:既可用n-config-provider+theme-overrides定制,也支持inline-theme-disabled模式下的主题类名方案(源码中useThemeClass('message', computed(() => props.type[0]), ...)即为该模式服务)。内置亮色/暗色主题定义位于 src/message/styles(light.ts/dark.ts),每种类型(info/success/warning/error/loading)都有独立的文字色、背景色、阴影与图标色变量。
Q & A:在 setup 外使用 Message
useMessage依赖 Vue 的provide/inject,因此无法直接在普通工具函数(如 axios 拦截器、路由守卫)中调用。官方文档提供了两种解决方案。
选择 1:使用 createDiscreteApi
使用 createDiscreteApi 创建一个"离散式"的 API 实例,绕过组件树的注入依赖。DiscreteApiOptions中messageProviderProps字段(src/discrete/src/interface.ts)接收MaybeRef<MessageProviderProps>,即可以传入n-message-provider支持的全部 Props 作为初始配置。基本用法:
import { createDiscreteApi } from 'naive-ui' const { message } = createDiscreteApi(['message'], { messageProviderProps: { placement: 'top-right' } }) // 在任何地方调用 message.success('操作成功')官方文档特别提醒:使用 createDiscreteApi 前请认真阅读它的注意事项,并且最好不要把createDiscreteApi和useMessage在同一 App 中混用——因为二者创建的是两套相互独立的消息实例与容器,混用可能导致行为不一致(例如两套容器位置、主题、zIndex 互相干扰)。
选择 2:把 message 挂载到 window
如果你只想在个别工具函数里使用,可以走"顶层 setup 预挂载"方案:在应用入口组件(位于n-message-provider内部)的setup中把useMessage()的返回值挂到window上,之后在任意 JS 文件中直接使用。调用前需要确保 message 已经挂载成功(即顶层组件已完成 setup)。
<!-- App.vue --> <n-message-provider> <content /> </n-message-provider><!-- content.vue --> <template>...</template> <script> import { useMessage } from 'naive-ui' import { defineComponent } from 'vue' // content export default defineComponent({ setup() { window.$message = useMessage() } }) </script>// xxx.js export function handler() { // 需要确保已经在 setup 中执行了 window.$message = message window.$message.success( 'Cause you walked hand in hand With another man in my place' ) }两种方案各有适用场景:选择 1适合需要独立、可控生命周期且要在大量非组件模块中使用的场景(注意与useMessage混用的警告);选择 2实现最简单,但依赖挂载时序,且会引入全局变量,适合小型项目中少量工具函数的临时调用。
结语与进一步阅读
Message 的完整使用链路是:n-message-provider(Teleport 到 body 的容器 + 全局默认配置)→useMessage()(从注入链获取命令式 API)→ 方法调用(返回响应式MessageReactive)→MessageEnvironment(定时器与悬浮控制)→Message(主题变量与图标渲染)。掌握 Provider 全局 Props、Option 单条覆盖、Reactive 实时修改与 setup 外调用方案,即可在任何业务场景中优雅地使用消息反馈。
进一步深入可参考仓库中的以下资源:
- 组件入口与类型导出:src/message/index.ts(
NMessageProvider、useMessage、MessageApi、MessageReactive等) - 完整源码:MessageProvider.tsx、Message.tsx、MessageEnvironment.tsx、message-props.ts、types.ts
- 全部演示:src/message/demos/zhCN(12 个 demo 对应本文各小节)
- 单元测试:src/message/tests/Message.spec.tsx(含交互与动画相关断言)、src/message/tests/server.spec.tsx(SSR 场景)
- 主题变量:src/message/styles/light.ts、src/message/styles/dark.ts、src/message/styles/_common.ts
【免费下载链接】naive-uiA Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.项目地址: https://gitcode.com/gh_mirrors/na/naive-ui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考