naive-ui Dialog 对话框:函数式 API 与组件式用法的完整指南
2026/9/21 16:14:11 网站建设 项目流程

naive-ui Dialog 对话框:函数式 API 与组件式用法的完整指南

【免费下载链接】naive-uiA Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.项目地址: https://gitcode.com/gh_mirrors/na/naive-ui

naive-ui的 Dialog 对话框组件同时提供了「函数式调用」(useDialog注入的dialog.warning(options)等形式)与「组件式使用」(<n-dialog>)两条使用路径,并允许通过n-dialog-provider集中管理所有对话框实例。本文以官方中文文档(src/dialog/demos/zhCN/index.demo-entry.md)为主线,结合仓库源码与全部演示示例,完整讲解使用前提、useDialog/useDialogReactiveListAPI、DialogOptions/DialogReactive全部配置项、组件 Props 与 Slots,以及底层实现原理,帮助你从“能跑通 Demo”进阶到“理解并驾驭对话框的每一个细节”。

阅读本文前,建议先浏览官方演示目录 src/dialog/demos/zhCN,其中包含 8 个可直接运行的.demo.vue示例。

使用前提:把组件放进n-dialog-provider

官方文档在开篇就用警示框强调了使用函数式 Dialog 的前提:

如果你想使用对话框,你需要把调用其方法的组件放在n-dialog-provider内部,并且使用useDialog去获取 API。

也就是说,函数式 Dialog 依赖**依赖注入(provide / inject)**机制工作,n-dialog-provider负责在组件树顶层注册 API,任何后代组件都可以通过useDialog()获取到同一个 API 对象。典型结构如下:

<!-- App.vue --> <n-dialog-provider> <content /> </n-dialog-provider>
import { useDialog } from 'naive-ui' import { defineComponent } from 'vue' // content export default defineComponent({ setup() { const dialog = useDialog() return { warning() { dialog.warning(options) } } } })

从源码看,useDialog的实现非常直接(src/dialog/src/composables.ts):

export function useDialog(): DialogApiInjection { const dialog = inject(dialogApiInjectionKey, null) if (dialog === null) { throwError('use-dialog', 'No outer <n-dialog-provider /> founded.') } return dialog }

可以看到:

  • 通过inject(dialogApiInjectionKey, null)从组件树上取 API;
  • 如果取不到(即没有外层n-dialog-provider),会直接抛出错误No outer <n-dialog-provider /> founded.

因此,凡是使用useDialog的组件,都必须保证其上方存在n-dialog-provider,否则运行时会报错而非静默失败。这与useMessageuseNotification等组件的使用方式一致。

另外,n-dialog-provider本身也接收两个可选 Props(见 DialogProvider.ts):

名称类型说明
injectionKeyString自定义注入的 key,用于在特殊场景下隔离/区分多个 provider
toString \| HTMLElement对话框挂载的目标位置,默认跟随 teleport 逻辑

useDialog API:五种入口方法

useDialog()返回的DialogApiInjection对象共暴露 5 个方法:

名称类型说明
destroyAll() => void销毁所有弹出的对话框
create(options: DialogOptions) => DialogReactive创建对话框(不预设类型)
error(options: DialogOptions) => DialogReactive调用error类型的对话框
info(options: DialogOptions) => DialogReactive调用info类型的对话框
success(options: DialogOptions) => DialogReactive调用success类型的对话框
warning(options: DialogOptions) => DialogReactive调用warning类型的对话框

在源码实现中(DialogProvider.ts),info/success/warning/error四个类型化方法其实都是create的包装:

const typedApi = ( ['info', 'success', 'warning', 'error'] as Array< 'info' | 'success' | 'warning' | 'error' > ).map(type => (options: DialogOptions): DialogReactive => { return create({ ...options, type }) })

也就是说,dialog.warning({ title: '...' })等价于dialog.create({ type: 'warning', title: '...' }),只是语法糖更直观。create本身会:

  1. createId()生成唯一key
  2. 把传入的optionskeydestroy一起包装成reactive对象(这正是后面“属性可动态修改”的基础);
  3. 推入dialogListRef,交由 provider 渲染(DialogProvider.ts)。

DialogOptions:函数式调用时的完整配置

调用dialog.create(options)时传入的optionsDialogOptions。下表为官方文档列出的全部属性(含默认值与引入版本):

名称类型默认值说明版本
action() => VNodeChildundefined操作区域的内容,需要是渲染函数
actionClassstringundefined操作区域的类名2.38.2
actionStyleObject \| stringundefined操作区域的样式2.38.2
autoFocusbooleantrue是否自动聚焦 Modal 第一个可聚焦的元素2.28.3
blockScrollbooleantrue是否在打开时禁用 body 滚动2.28.3
borderedbooleanfalse是否显示border
classanyundefined类名2.33.0
closablebooleantrue是否显示close图标
closeFocusablebooleanfalse关闭按钮是否可以聚焦2.43.0
closeOnEscbooleantrue是否在摁下 Esc 键的时候关闭对话框2.26.4
contentstring \| (() => VNodeChild)undefined对话框内容,可以是渲染函数
contentClassstringundefined内容的类名2.38.2
contentStyleObject \| stringundefined内容的样式2.38.2
draggableboolean \| { bounds?: 'none' }false是否可拖拽2.41.0
iconPlacement'left' \| 'top''left'图标的位置
icon() => VNodeChildundefined对话框icon,需要是渲染函数
loadingbooleanfalse是否显示loading状态
maskClosablebooleantrue是否可以通过点击mask关闭对话框
negativeButtonPropsButtonPropsundefined取消按钮的属性2.27.0
negativeTextstringundefined取消按钮的文字,不填对应的按钮不会出现
positiveButtonPropsButtonPropsundefined确认按钮的属性2.27.0
positiveTextstringundefined确认按钮的文字,不填对应的按钮不会出现
showIconbooleantrue是否显示icon
stylestring \| Objectundefined样式
titlestring \| (() => VNodeChild)undefined标题,可以是渲染函数
titleClassstringundefined标题的类名2.38.2
titleStyleObject \| stringundefined标题的样式2.38.2
transformOrigin'mouse' \| 'center''mouse'对话框动画出现的位置2.34.0
type'error' \| 'success' \| 'warning''warning'对话框类型
zIndexnumberundefinedDialog 的 z-index2.43.0
onAfterEnter() => voidundefined出现动画完成执行的回调2.33.0
onAfterLeave() => voidundefined关闭动画完成执行的回调2.33.3
onClose() => boolean \| Promise<boolean> \| anyundefined默认行为是关闭确认框。返回false或者resolve false或者Promisereject会避免默认行为
onNegativeClick(e: MouseEvent) => boolean \| Promise<boolean> \| anyundefined默认行为是关闭确认框。返回false或者resolve false或者Promisereject会避免默认行为
onPositiveClick(e: MouseEvent) => boolean \| Promise<boolean> \| anyundefined默认行为是关闭确认框。返回false或者resolve false或者Promisereject会避免默认行为
onMaskClick() => voidundefined点击蒙层后执行的回调

几个容易被忽视的细节

  • positiveText/negativeText决定按钮是否出现:不填对应文字,则对应按钮不会渲染。这是函数式 Dialog 最常用的“二选一/不显示按钮”控制手段。
  • onPositiveClick等回调的“拦截”语义:默认行为是关闭对话框;只要回调返回falseresolve(false)或 Promise 被reject,就会阻止默认的关闭行为。这一点在“异步确认”场景中非常有用(详见下文异步示例)。
  • transformOrigin: 'mouse':对话框的出现动画会以鼠标点击位置为原点展开,这是 naive-ui 对话框的默认行为,交互上更“跟手”;可改为'center'让动画从屏幕中心展开。其底层通过useClickeduseClickPosition(来自vooks)记录点击位置,见 DialogProvider.ts。
  • draggable: { bounds?: 'none' }:自 2.41.0 起支持拖拽,可通过bounds: 'none'允许拖出可视区域边界。
  • 渲染函数型属性titlecontenticonaction都支持传渲染函数(() => VNodeChild),可以实现任意复杂的自定义内容。

DialogReactive:动态修改与主动销毁

dialog.xxx(options)的返回值是DialogReactive,它由options加两个只读字段组成(DialogProvider.ts):

export interface DialogReactive extends DialogOptions { readonly key: string readonly destroy: () => void }

DialogReactive Properties

官方文档明确说明:下列属性都可以被动态修改(修改后对话框会实时响应)。

名称类型说明版本
actionClassstring操作区域的类名2.38.2
actionStyleObject \| string操作区域的样式2.38.2
borderedboolean是否显示border
classany类名2.33.0
closableboolean是否显示close图标
closeFocusableboolean关闭按钮是否可以聚焦2.43.0
closeOnEscboolean是否在摁下 Esc 键的时候关闭对话框2.26.4
contentstring \| (() => VNodeChild)对话框内容,可以是渲染函数
contentClassstring内容的类名2.38.2
contentStyleObject \| string内容的样式2.38.2
iconPlacement'left' \| 'top'图标的位置
icon() => VNodeChild对话框icon,需要是渲染函数
loadingboolean是否显示loading状态
maskClosableboolean是否可以通过点击mask关闭对话框
negativeButtonPropsButtonProps取消按钮的属性2.27.0
negativeTextstring取消按钮的文字,不填对应的按钮不会出现
positiveButtonPropsButtonProps确认按钮的属性2.27.0
positiveTextstring确认按钮的文字,不填对应的按钮不会出现
showIconboolean是否显示icon
stylestring \| Object样式
titlestring \| (() => VNodeChild)可以是渲染函数
titleClassstring标题的类名2.38.2
titleStyleObject \| string标题的样式2.38.2
transformOrigin'mouse' \| 'center'对话框动画出现的位置2.34.0
type'error' \| 'success' \| 'warning'对话框类型
onAfterEnter() => void \| undefined出现动画完成执行的回调2.33.0
onAfterLeave() => void \| undefined关闭动画完成执行的回调2.33.3
onClose() => boolean \| Promise<boolean> \| any默认行为是关闭确认框。返回false或者resolve false或者Promisereject会避免默认行为
onEsc() => void焦点在 dialog 内部时按下 Esc 键的回调2.32.0
onNegativeClick(e: MouseEvent) => boolean \| Promise<boolean> \| any默认行为是关闭确认框。返回false或者resolve false或者Promisereject会避免默认行为
onPositiveClick(e: MouseEvent) => boolean \| Promise<boolean> \| any默认行为是关闭确认框。返回false或者resolve false或者Promisereject会避免默认行为

相比DialogOptionsDialogReactive额外暴露了onEsc回调(2.32.0),用于监听焦点在对话框内部时按下 Esc 键的事件;onAfterEnter/onAfterLeave的类型标注为() => void | undefined

DialogReactive Methods

名称类型说明
destroy()关闭Dialog

在源码中,destroy被实现为调用对应对话框实例的hide()方法(DialogProvider.ts):

destroy: () => { dialogInstRefs[`n-dialog-${key}`]?.hide() }

destroyAll()则遍历所有实例统一调用hide()(DialogProvider.ts)。对话框关闭动画结束后,handleAfterLeave会把对应实例从dialogListRef中移除(DialogProvider.ts)。

组件式用法:n-dialog Props 与 Slots

除了函数式调用,<n-dialog>也可以像普通组件一样直接写在模板中(官方演示 use-component.demo.vue 即为此用法):

<template> <n-dialog title="确认" content="你确定" negative-text="不确认" positive-text="确认" @positive-click="handlePositiveClick" @negative-click="handleNegativeClick" /> </template>

Dialog Props

名称类型默认值说明版本
action-classstringundefined操作区域的类名2.38.2
action-styleObject \| stringundefined操作区域的样式2.38.2
borderedbooleanfalse是否显示border
closablebooleantrue是否显示close图标
close-focusablebooleanfalse关闭按钮是否可以聚焦2.43.0
contentstring \| (() => VNodeChild)undefined对话框内容,可以是渲染函数
content-classstringundefined内容的类名2.38.2
content-styleObject \| stringundefined内容的样式2.38.2
icon-placement'left' \| 'top''left'图标放置的位置
icon() => VNodeChildundefined需要是渲染函数
loadingbooleanfalse是否显示loading状态
negative-button-propsButtonPropsundefined取消按钮的属性2.27.0
negative-textstringundefined取消按钮的文字,不填对应的按钮不会出现
positive-button-propsButtonPropsundefined确认按钮的属性2.27.0
positive-textstringundefined确认按钮的文字,不填对应的按钮不会出现
show-iconbooleantrue是否显示icon
titlestring \| (() => VNodeChild)undefined对话框标题,可以是渲染函数
title-classstringundefined标题的类名2.38.2
title-styleObject \| stringundefined标题的样式2.38.2
type'error' \| 'success' \| 'warning' \| 'info''warning'对话框类型
on-close() => voidundefined点击关闭时执行的回调函数
on-negative-click(e: MouseEvent) => voidundefined执行negative时执行的回调函数
on-positive-click(e: MouseEvent) => voidundefined执行positive时执行的回调函数

注意两点差异:

  • 组件 Props 采用kebab-case命名(action-classpositive-text等);
  • 组件模式下type支持四种值(比DialogOptions多一个'info'),且事件回调类型为() => void(不参与关闭拦截逻辑,拦截能力仅存在于函数式 API 中)。

Dialog Slots

名称参数说明版本
action()action内容
default()对话框内容
header()header内容
icon()icon内容
close()close内容2.36.0

官方演示逐例解读

官方中文文档在“演示”一节中按顺序引入了 8 个示例(src/dialog/demos/zhCN),下面逐一解读其核心要点。

1. 基础用法(basic.demo.vue)

通过dialog.warning / success / error分别触发三种类型的对话框,演示了titlecontentpositiveTextnegativeTextdraggable及两个点击回调的常规组合:

dialog.warning({ title: '警告', content: '你确定?', positiveText: '确定', negativeText: '不确定', draggable: true, onPositiveClick: () => message.success('确定'), onNegativeClick: () => message.error('不确定') })

这是日常“删除确认”“操作确认”弹窗的最典型写法:类型(图标与配色)+ 标题 + 内容 + 确认/取消文案 + 回调。

2. 异步(async.demo.vue)

演示了onPositiveClick返回 Promise 时对话框会保持打开并进入 loading 状态,直到 Promise resolve 才关闭。示例中点击确认后依次展示“倒计时 3 秒 → 2 秒 → 1 秒 → 0 秒”,期间通过修改响应式属性d.loadingd.content实时驱动 UI:

const d = dialog.success({ title: '异步', content: '点击,倒计时 3 秒', positiveText: '确认', onPositiveClick: () => { d.loading = true return new Promise((resolve) => { sleep() .then(() => { d.content = countDown(2); return sleep() }) .then(() => { d.content = countDown(1); return sleep() }) .then(() => { d.content = countDown(0) }) .then(resolve) }) } })

这正是前面提到的“回调拦截 + 动态属性”两大能力的组合应用:回调返回 Promise,关闭行为被挂起,d.loading控制确认按钮 loading,d.content实时更新内容。

3. 使用组件(use-component.demo.vue)

直接使用<n-dialog>组件,通过@positive-click/@negative-click事件与negative-text/positive-textProps 完成交互,适合把对话框作为页面内固定区块(而非浮层)使用的场景。

4. 点击遮罩(mask.demo.vue)

演示maskClosable: false禁止点击遮罩关闭,同时通过onMaskClick在点击遮罩时给出提示、通过onEsc监听键盘 Esc:

dialog.success({ title: '关闭', content: '你确定?', positiveText: '确定', negativeText: '不确定', maskClosable: false, onMaskClick: () => message.success('不能关闭'), onEsc: () => message.success('通过 esc 关闭') })

适合“强确认”场景(如不可撤销的删除操作),强制用户只能通过明确按钮关闭。

5. 自定义 Action(action.demo.vue)

演示titlecontentaction三个渲染函数属性,任意输出自定义 VNode:

dialog.warning({ title: '使用渲染函数', content: () => 'Content', action: () => 'Action' })

当默认的确认/取消按钮无法满足需求(例如需要嵌入表单、多按钮或自定义布局)时,可完全接管action区域的内容。

6. 访问全部 Dialog 实例(use-dialog-reactive-list.demo.vue)

使用useDialogReactiveList()获取当前n-dialog-provider下全部对话框实例的响应式数组,直接渲染数量:

const dialogReactiveList = useDialogReactiveList() // 模板中:目前页面中共有 {{ dialogReactiveList.length }} 个对话框。

useDialogReactiveList的实现同样基于依赖注入(composables.ts),返回的是Ref<readonly DialogReactive[]>;由于 provider 内部维护的是同一个dialogListRef,列表会随对话框的创建与销毁自动增删。官方文档给出的类型签名为:

() => Ref<readonly DialogReactive[]>

7. Focus debug(focus-debug.demo.vue)

该示例通过渲染函数在content中放置两个NTimePicker,用于验证对话框打开后的焦点管理行为(配合autoFocuscloseFocusable等配置调试键盘可达性)。

8. RTL debug(rtl-debug.demo.vue)

用于验证n-config-provider开启 RTL(从右到左)布局后对话框的样式表现,样式定义可参见 src/dialog/src/styles/rtl.cssr.ts。

底层原理:DialogProvider 的完整工作流

综合 DialogProvider.ts 与 composables.ts,函数式 Dialog 的完整调用链可以概括为:

  1. 注册NDialogProvider.setup中创建dialogListRefdialogInstRefs,并把 API 对象与响应式列表通过三个 injection key 注入组件树(dialogApiInjectionKeydialogProviderInjectionKeydialogReactiveListInjectionKey),见 context.ts 与 DialogProvider.ts。
  2. 创建:调用dialog.warning(options)create({ ...options, type })→ 生成key、包装成reactive对象、pushdialogListRef
  3. 渲染:provider 的render()遍历dialogList,为每个实例渲染一个NDialogEnvironment(DialogEnvironment.tsx),并把destroystyle剥离、以internalStyle/internalKey传入;每个实例通过ref回调登记到dialogInstRefs,以便destroy/destroyAll找到它。
  4. 销毁destroy()/destroyAll()调用hide()播放关闭动画,动画结束后handleAfterLeave将实例从列表移除,实现“创建即入列、销毁即出列”的完整生命周期。

函数式 Dialog 的核心优势由此体现:调用方无需维护组件挂载状态,所有实例由 provider 统一管理,并且由于DialogReactive是响应式对象,调用方持有引用后可以随时动态修改任意属性——这是组件式<n-dialog>难以直接实现的“远程控制”能力。

在测试层面,仓库提供了 src/dialog/tests/Dialog.spec.tsx 覆盖交互行为,以及 server.spec.tsx 验证服务端渲染(SSR)场景下的稳定性;对话框的明暗主题变量定义在 src/dialog/styles/light.ts 与 src/dialog/styles/dark.ts,可通过主题覆盖机制统一定制。

结语

naive-ui 的 Dialog 组件在设计上提供了“双轨制”使用方式:函数式 API(useDialog+DialogOptions)适合需要程序化控制、异步确认、动态更新的场景;组件式<n-dialog>适合在模板中静态声明、以事件驱动交互的场景。官方中文文档(src/dialog/demos/zhCN/index.demo-entry.md)中列出的全部 API 表格与 8 个演示示例覆盖了从基础确认框到异步倒计时、从遮罩控制到自定义渲染函数的完整能力图谱;结合 DialogProvider.ts 的实现,可以清晰理解其依赖注入、响应式实例管理与生命周期清理机制,从而在真实项目中得心应手地使用与调试。

【免费下载链接】naive-uiA Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.项目地址: https://gitcode.com/gh_mirrors/na/naive-ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询