refactor(Dialog): redesign function-call API (#10781)

* refactor(Dialog): re-design function-call API

* chore: remove invalid char

* docs: order

* chore: remove var
This commit is contained in:
neverland 2022-07-03 12:28:25 +08:00 committed by GitHub
parent e27efdad0d
commit e5e6e8aaa4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 217 additions and 211 deletions

View File

@ -62,6 +62,64 @@ Area 组件是基于 Picker 组件进行封装的,因此本次升级也对 Are
## API 调整
### Dialog 调用方式调整
在 Vant 3 中,`Dialog` 是一个函数,调用函数可以快速唤起全局的弹窗组件,而 `Dialog.Component` 才是 `Dialog` 组件对象,这与大部分组件的用法存在差异,容易导致使用错误。
为了更符合直觉,我们在 Vant 4 中调整了 `Dialog` 的调用方式,将 `Dialog()` 函数重命名为 `openDialog()`
```js
// Vant 3
Dialog(); // 函数调用
Dialog.Component; // 组件对象
// Vant 4
openDialog(); // 函数调用
Dialog; // 组件对象
```
`Dialog` 上挂载的其他方法也进行了重命名,新旧 API 的映射关系如下:
```js
Dialog(); // -> openDialog()
Dialog.alert(); // -> openDialog()
Dialog.confirm(); // -> openConfirmDialog()
Dialog.close(); // -> closeDialog();
Dialog.setDefaultOptions(); // -> setDialogDefaultOptions()
Dialog.resetDefaultOptions(); // -> resetDialogDefaultOptions()
```
同时Vant 4 将不再在 `this` 对象上全局注册 `$dialog` 方法,这意味着 `this` 对象上将无法访问到 `$dialog`
```js
export default {
mounted() {
// 无效代码
this.$dialog.alert({
message: '弹窗内容',
});
},
};
```
大多数场景下,推荐通过 `import` 引入对应的函数进行使用。
如果需要全局方法,可以手动在 `app` 对象上注册:
```js
import { openDialog } from 'vant';
// 注册 $dialog 方法
app.config.globalProperties.$dialog = openDialog;
// 添加 TS 类型定义
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$dialog: typeof openDialog;
}
}
```
### 事件命名调整
从 Vant 4 开始,所有的事件均采用 Vue 官方推荐的**驼峰格式**进行命名。

View File

@ -2,7 +2,7 @@
### Intro
A modal box pops up on the page, which is often used for message prompts, message confirmation, or to complete specific interactive operations in the current page. It supports two methods: function call and component call.
A modal box pops up on the page, which is often used for message prompts, message confirmation, or to complete specific interactive operations in the current page. It supports two methods: component call and function call.
### Install
@ -16,6 +16,18 @@ const app = createApp();
app.use(Dialog);
```
### Function Call
Vant provides some utility functions that can quickly evoke global `Dialog` components.
For example, calling the `openDialog` function will render a Dialog directly in the page.
```js
import { openDialog } from 'vant';
openDialog({ message: 'Content' });
```
## Usage
### Alert dialog
@ -23,14 +35,14 @@ app.use(Dialog);
Used to prompt for some messages, only including one confirm button.
```js
Dialog.alert({
openDialog({
title: 'Title',
message: 'Content',
}).then(() => {
// on close
});
Dialog.alert({
openDialog({
message: 'Content',
}).then(() => {
// on close
@ -42,7 +54,7 @@ Dialog.alert({
Used to confirm some messages, including a confirm button and a cancel button.
```js
Dialog.confirm({
openConfirmDialog({
title: 'Title',
message: 'Content',
})
@ -59,7 +71,7 @@ Dialog.confirm({
Use round button style.
```js
Dialog.alert({
openDialog({
title: 'Title',
message: 'Content',
theme: 'round-button',
@ -67,7 +79,7 @@ Dialog.alert({
// on close
});
Dialog.alert({
openDialog({
message: 'Content',
theme: 'round-button',
}).then(() => {
@ -85,27 +97,13 @@ const beforeClose = (action) =>
}, 1000);
});
Dialog.confirm({
openConfirmDialog({
title: 'Title',
message: 'Content',
beforeClose,
});
```
### Global Method
After registering the Dialog component through `app.use`, the `$dialog` method will be automatically mounted on all subComponents of the app.
```js
export default {
mounted() {
this.$dialog.alert({
message: 'Content',
});
},
};
```
### Advanced Usage
If you need to render vue components within a dialog, you can use dialog component.
@ -133,12 +131,11 @@ export default {
| Name | Description | Attribute | Return value |
| --- | --- | --- | --- |
| Dialog | Show dialog | _options: DialogOptions_ | `Promise<void>` |
| Dialog.alert | Show alert dialog | _options: DialogOptions_ | `Promise<void>` |
| Dialog.confirm | Show confirm dialog | _options: DialogOptions_ | `Promise<void>` |
| Dialog.setDefaultOptions | Set default options of all dialogs | _options: DialogOptions_ | `void` |
| Dialog.resetDefaultOptions | Reset default options of all dialogs | - | `void` |
| Dialog.close | Close dialog | - | `void` |
| openDialog | Show dialog | _options: DialogOptions_ | `Promise<void>` |
| openConfirmDialog | Show confirm dialog | _options: DialogOptions_ | `Promise<void>` |
| closeDialog | Close dialog | - | `void` |
| setDialogDefaultOptions | Set default options of all dialogs | _options: DialogOptions_ | `void` |
| resetDialogDefaultOptions | Reset default options of all dialogs | - | `void` |
### DialogOptions

View File

@ -2,51 +2,30 @@
### 介绍
弹出模态框,常用于消息提示、消息确认,或在当前页面内完成特定的交互操作,支持函数调用和组件调用两种方式。
弹出模态框,常用于消息提示、消息确认,或在当前页面内完成特定的交互操作。支持组件调用和函数调用两种方式。
### 函数调用
### 引入
`Dialog` 是一个函数,调用后会直接在页面中弹出相应的模态框。
```js
import { Dialog } from 'vant';
Dialog({ message: '提示' });
```
### 组件调用
通过组件调用 Dialog 时,可以通过下面的方式进行注册:
通过以下方式来全局注册组件,更多注册方式请参考[组件注册](#/zh-CN/advanced-usage#zu-jian-zhu-ce)。
```js
import { createApp } from 'vue';
import { Dialog } from 'vant';
// 全局注册
const app = createApp();
app.use(Dialog);
// 局部注册
export default {
components: {
[Dialog.Component.name]: Dialog.Component,
},
};
```
`script setup` 中,可以通过以下方式使用:
### 函数调用
```html
<script setup>
const VanDialog = Dialog.Component;
</script>
为了便于使用 `Dialog`Vant 提供了一系列辅助函数,通过辅助函数可以快速唤起全局的弹窗组件。
<template>
<!-- 中划线命名 -->
<van-dialog />
<!-- 也支持大驼峰命名 -->
<VanDialog>
</template>
比如使用 `openDialog` 函数,调用后会直接在页面中渲染对应的弹出框。
```js
import { openDialog } from 'vant';
openDialog({ message: '提示' });
```
## 代码演示
@ -56,14 +35,16 @@ export default {
用于提示一些消息,只包含一个确认按钮。
```js
Dialog.alert({
import { openDialog } from 'vant';
openDialog({
title: '标题',
message: '代码是写出来给人看的,附带能在机器上运行。',
}).then(() => {
// on close
});
Dialog.alert({
openDialog({
message: '生命远不止连轴转和忙到极限,人类的体验远比这辽阔、丰富得多。',
}).then(() => {
// on close
@ -75,7 +56,9 @@ Dialog.alert({
用于确认消息,包含取消和确认按钮。
```js
Dialog.confirm({
import { openConfirmDialog } from 'vant';
openConfirmDialog({
title: '标题',
message:
'如果解决方法是丑陋的,那就肯定还有更好的解决方法,只是还没有发现而已。',
@ -93,7 +76,9 @@ Dialog.confirm({
将 theme 选项设置为 `round-button` 可以展示圆角按钮风格的弹窗。
```js
Dialog.alert({
import { openDialog } from 'vant';
openDialog({
title: '标题',
message: '代码是写出来给人看的,附带能在机器上运行。',
theme: 'round-button',
@ -101,7 +86,7 @@ Dialog.alert({
// on close
});
Dialog.alert({
openDialog({
message: '生命远不止连轴转和忙到极限,人类的体验远比这辽阔、丰富得多。',
theme: 'round-button',
}).then(() => {
@ -114,6 +99,8 @@ Dialog.alert({
通过 `beforeClose` 属性可以传入一个回调函数,在弹窗关闭前进行特定操作。
```js
import { openConfirmDialog } from 'vant';
const beforeClose = (action) =>
new Promise((resolve) => {
setTimeout(() => {
@ -126,7 +113,7 @@ const beforeClose = (action) =>
}, 1000);
});
Dialog.confirm({
openConfirmDialog({
title: '标题',
message:
'如果解决方法是丑陋的,那就肯定还有更好的解决方法,只是还没有发现而已。',
@ -134,22 +121,6 @@ Dialog.confirm({
});
```
### 全局方法
通过 `app.use` 全局注册 Dialog 组件后,会自动在 app 的所有子组件上挂载 `$dialog` 方法,在所有组件内部都可以直接调用此方法。
```js
export default {
mounted() {
this.$dialog.alert({
message: '弹窗内容',
});
},
};
```
> Tips: 由于 setup 选项中无法访问 this因此不能使用上述方式请通过 import 引入。
### 组件调用
如果需要在弹窗内嵌入组件或其他自定义内容,可以使用组件调用的方式。
@ -177,12 +148,11 @@ export default {
| 方法名 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| Dialog | 展示弹窗 | _options: DialogOptions_ | `Promise<void>` |
| Dialog.alert | 展示消息提示弹窗 | _options: DialogOptions_ | `Promise<void>` |
| Dialog.confirm | 展示消息确认弹窗 | _options: DialogOptions_ | `Promise<void>` |
| Dialog.setDefaultOptions | 修改默认配置,对所有 Dialog 生效 | _options: DialogOptions_ | `void` |
| Dialog.resetDefaultOptions | 重置默认配置,对所有 Dialog 生效 | - | `void` |
| Dialog.close | 关闭弹窗 | - | `void` |
| openDialog | 展示弹窗 | _options: DialogOptions_ | `Promise<void>` |
| openConfirmDialog | 展示消息确认弹窗 | _options: DialogOptions_ | `Promise<void>` |
| closeDialog | 关闭弹窗 | - | `void` |
| setDialogDefaultOptions | 修改默认配置,影响所有的 `openDialog` 调用 | _options: DialogOptions_ | `void` |
| resetDialogDefaultOptions | 重置默认配置,影响所有的 `openDialog` 调用 | - | `void` |
### DialogOptions

View File

@ -1,12 +1,10 @@
<script setup lang="ts">
import VanCell from '../../cell';
import { Dialog } from '..';
import { openDialog, openConfirmDialog, Dialog as VanDialog } from '..';
import { ref } from 'vue';
import { cdnURL, useTranslate } from '../../../docs/site';
import type { DialogAction } from '../types';
const VanDialog = Dialog.Component;
const t = useTranslate({
'zh-CN': {
title: '标题',
@ -39,20 +37,20 @@ const show = ref(false);
const image = cdnURL('apple-3.jpeg');
const onClickAlert = () => {
Dialog.alert({
openDialog({
title: t('title'),
message: t('content1'),
});
};
const onClickAlert2 = () => {
Dialog.alert({
openDialog({
message: t('content2'),
});
};
const onClickRound = () => {
Dialog.alert({
openDialog({
theme: 'round-button',
title: t('title'),
message: t('content1'),
@ -60,14 +58,14 @@ const onClickRound = () => {
};
const onClickRound2 = () => {
Dialog.alert({
openDialog({
theme: 'round-button',
message: t('content2'),
});
};
const onClickConfirm = () => {
Dialog.confirm({
openConfirmDialog({
title: t('title'),
message: t('content3'),
});
@ -79,7 +77,7 @@ const onClickBeforeClose = () => {
setTimeout(() => resolve(action === 'confirm'), 1000);
});
Dialog.confirm({
openConfirmDialog({
title: t('title'),
message: t('content3'),
beforeClose,

View File

@ -1,44 +1,11 @@
import { App } from 'vue';
import { extend, inBrowser, withInstall, ComponentInstance } from '../utils';
import { extend, inBrowser, ComponentInstance } from '../utils';
import { mountComponent, usePopupState } from '../utils/mount-component';
import VanDialog from './Dialog';
import Dialog from './Dialog';
import type { DialogAction, DialogOptions } from './types';
let instance: ComponentInstance;
function initInstance() {
const Wrapper = {
setup() {
const { state, toggle } = usePopupState();
return () => <VanDialog {...state} onUpdate:show={toggle} />;
},
};
({ instance } = mountComponent(Wrapper));
}
function Dialog(options: DialogOptions) {
/* istanbul ignore if */
if (!inBrowser) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
if (!instance) {
initInstance();
}
instance.open(
extend({}, Dialog.currentOptions, options, {
callback: (action: DialogAction) => {
(action === 'confirm' ? resolve : reject)(action);
},
})
);
});
}
Dialog.defaultOptions = {
const DEFAULT_OPTIONS = {
title: '',
width: '',
theme: null,
@ -64,34 +31,55 @@ Dialog.defaultOptions = {
showCancelButton: false,
closeOnPopstate: true,
closeOnClickOverlay: false,
} as const;
let currentOptions = extend({}, DEFAULT_OPTIONS);
function initInstance() {
const Wrapper = {
setup() {
const { state, toggle } = usePopupState();
return () => <Dialog {...state} onUpdate:show={toggle} />;
},
};
Dialog.currentOptions = extend({}, Dialog.defaultOptions);
({ instance } = mountComponent(Wrapper));
}
Dialog.alert = Dialog;
export function openDialog(options: DialogOptions) {
/* istanbul ignore if */
if (!inBrowser) {
return Promise.resolve();
}
Dialog.confirm = (options: DialogOptions) =>
Dialog(extend({ showCancelButton: true }, options));
return new Promise((resolve, reject) => {
if (!instance) {
initInstance();
}
Dialog.close = () => {
instance.open(
extend({}, currentOptions, options, {
callback: (action: DialogAction) => {
(action === 'confirm' ? resolve : reject)(action);
},
})
);
});
}
export const setDialogDefaultOptions = (options: DialogOptions) => {
extend(currentOptions, options);
};
export const resetDialogDefaultOptions = () => {
currentOptions = extend({}, DEFAULT_OPTIONS);
};
export const openConfirmDialog = (options: DialogOptions) =>
openDialog(extend({ showCancelButton: true }, options));
export const closeDialog = () => {
if (instance) {
instance.toggle(false);
}
};
Dialog.setDefaultOptions = (options: DialogOptions) => {
extend(Dialog.currentOptions, options);
};
Dialog.resetDefaultOptions = () => {
Dialog.currentOptions = extend({}, Dialog.defaultOptions);
};
Dialog.Component = withInstall(VanDialog);
Dialog.install = (app: App) => {
app.use(Dialog.Component);
app.config.globalProperties.$dialog = Dialog;
};
export { Dialog };

View File

@ -1,7 +1,16 @@
import { Dialog } from './function-call';
import { withInstall } from '../utils';
import _Dialog from './Dialog';
export const Dialog = withInstall(_Dialog);
export default Dialog;
export { Dialog };
export {
openDialog,
closeDialog,
openConfirmDialog,
setDialogDefaultOptions,
resetDialogDefaultOptions,
} from './function-call';
export type { DialogProps } from './Dialog';
export type {
DialogTheme,
@ -12,6 +21,6 @@ export type {
declare module 'vue' {
export interface GlobalComponents {
VanDialog: typeof Dialog.Component;
VanDialog: typeof Dialog;
}
}

View File

@ -1,9 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should allow to render JSX message 1`] = `
<div class="van-dialog__message">
<div>
foo
</div>
</div>
`;

View File

@ -1,28 +1,30 @@
import { createApp } from 'vue';
import { later } from '../../../test';
import { Dialog } from '../function-call';
import DialogComponent from '../Dialog';
import {
openDialog,
closeDialog,
setDialogDefaultOptions,
resetDialogDefaultOptions,
} from '../function-call';
test('should update default options when calling setDefaultOptions method', () => {
Dialog.setDefaultOptions({ lockScroll: false });
expect(Dialog.currentOptions.lockScroll).toBeFalsy();
Dialog.resetDefaultOptions();
expect(Dialog.currentOptions.lockScroll).toBeTruthy();
});
test('should expose Dialog component', () => {
expect(Dialog.Component.name).toEqual('van-dialog');
});
test('should register component to app', () => {
const app = createApp();
app.use(Dialog);
expect(app.component(DialogComponent.name)).toBeTruthy();
});
test('should render dialog after calling Dialog', async () => {
const wrapper = document.createElement('div');
Dialog.alert({
setDialogDefaultOptions({ message: 'foo', teleport: wrapper, });
openDialog();
await later();
const dialog = wrapper.querySelector('.van-dialog');
expect(dialog.innerHTML.includes('foo')).toBeTruthy();
resetDialogDefaultOptions();
openDialog({ teleport: wrapper });
await later();
const dialog2 = wrapper.querySelector('.van-dialog');
expect(dialog2.innerHTML.includes('foo')).toBeFalsy();
});
test('should render dialog after calling openDialog', async () => {
const wrapper = document.createElement('div');
openDialog({
message: '1',
teleport: wrapper,
});
@ -32,9 +34,9 @@ test('should render dialog after calling Dialog', async () => {
expect(dialog).toBeTruthy();
});
test('should close dialog after calling Dialog.call', async () => {
test('should close dialog after calling closeDialog', async () => {
const wrapper = document.createElement('div');
Dialog.alert({
openDialog({
message: '1',
teleport: wrapper,
});
@ -43,7 +45,7 @@ test('should close dialog after calling Dialog.call', async () => {
const dialog = wrapper.querySelector('.van-dialog');
expect(dialog.style.display).toEqual('');
Dialog.close();
closeDialog();
await later();
expect(dialog.className.split(' ')).toContain(
'van-dialog-bounce-leave-active'
@ -52,7 +54,7 @@ test('should close dialog after calling Dialog.call', async () => {
test('should allow to render JSX message', async () => {
const wrapper = document.createElement('div');
Dialog.alert({
openDialog({
message: () => <div>foo</div>,
teleport: wrapper,
});

View File

@ -1,4 +1,3 @@
import { Dialog } from './function-call';
import type { CSSProperties, TeleportProps } from 'vue';
import type { Interceptor, Numeric } from '../utils';
@ -33,9 +32,3 @@ export type DialogOptions = {
confirmButtonDisabled?: boolean;
closeOnClickOverlay?: boolean;
};
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$dialog: typeof Dialog;
}
}

View File

@ -77,7 +77,7 @@ app.use(SwipeCell);
```
```js
import { Dialog } from 'vant';
import { openConfirmDialog } from 'vant';
export default {
setup() {
@ -89,7 +89,7 @@ export default {
return true;
case 'right':
return new Promise((resolve) => {
Dialog.confirm({
openConfirmDialog({
title: 'Are you sure to delete?',
}).then(resolve);
});

View File

@ -83,7 +83,7 @@ app.use(SwipeCell);
```
```js
import { Dialog } from 'vant';
import { openConfirmDialog } from 'vant';
export default {
setup() {
@ -96,7 +96,7 @@ export default {
return true;
case 'right':
return new Promise((resolve) => {
Dialog.confirm({
openConfirmDialog({
title: '确定删除吗?',
}).then(resolve);
});

View File

@ -4,7 +4,7 @@ import VanButton from '../../button';
import VanCell from '../../cell';
import VanCard from '../../card';
import { cdnURL, useTranslate } from '../../../docs/site';
import { Dialog } from '../../dialog';
import { openConfirmDialog } from '../../dialog';
const t = useTranslate({
'zh-CN': {
@ -39,7 +39,7 @@ const beforeClose = ({ position }: { position: string }) => {
return true;
case 'right':
return new Promise<boolean>((resolve) => {
Dialog.confirm({
openConfirmDialog({
title: t('confirm'),
}).then(() => {
resolve(true);

View File

@ -100,13 +100,13 @@ Using `node` slot to custom the content of the node.
```js
import { ref } from 'vue';
import { Dialog } from 'vant';
import { openConfirmDialog } from 'vant';
export default {
setup() {
const checked = ref(true);
const onUpdateValue = (newValue) => {
Dialog.confirm({
openConfirmDialog({
title: 'Confirm',
message: 'Are you sure to toggle switch?',
}).then(() => {

View File

@ -112,13 +112,13 @@ export default {
```js
import { ref } from 'vue';
import { Dialog } from 'vant';
import { openConfirmDialog } from 'vant';
export default {
setup() {
const checked = ref(true);
const onUpdateValue = (newValue) => {
Dialog.confirm({
openConfirmDialog({
title: '提醒',
message: '是否切换开关?',
}).then(() => {

View File

@ -4,7 +4,7 @@ import VanCell from '../../cell';
import VanIcon from '../../icon';
import { ref } from 'vue';
import { useTranslate } from '../../../docs/site';
import { Dialog } from '../../dialog';
import { openConfirmDialog } from '../../dialog';
const t = useTranslate({
'zh-CN': {
@ -36,7 +36,7 @@ const checked4 = ref(true);
const checked5 = ref(true);
const onUpdateValue = (checked: boolean) => {
Dialog.confirm({
openConfirmDialog({
title: t('title'),
message: t('message'),
}).then(() => {