Open worldzhao opened 1 year ago
笔者去年写过一篇 NiceModal:重新思考 React 中的弹窗使用方式,6 月中旬换了工作,技术栈也由 React + TypeScript 切成了 Vue@2.7 + JavaScript :),但是没有发现 @ebay/nice-modal-react 在 vue 生态中的替代方案,故而有了这篇文章。
对于 React 技术栈写过一篇 2021-2022,我的前端最佳实践,有兴趣的同学可以参考阅读。
如果本文能对你日常开发产生一些新的思考和效率的提升,那就太好了!笔者在工作中接触 vue 的时间也就短短半个月,如有设计不当的地方欢迎 PR 一起改进。 Vue Nice Modal - GitHub
在开始进入正题之前,先看看一些与弹窗有关的有趣场景 🤔。
上图是掘金的登录弹窗,未登录状态下触发该弹窗展示的时机有很多,比如:
开发者往往会基于第三方组件库定义一个 <LoginModal />,然后将其挂载至 Root 组件下。
<LoginModal />
Root
这样会带来一些问题:
setVisible
setOtherLoginData
<Main />
展示一个弹窗,为什么会变得如此复杂?
以上案例来自 @ebay/nice-modal-react,稍作修改
除了上述全局弹窗的场景,还有一种场景也很让人头疼。
用户头疼,开发者也头疼
弹窗 1 确认弹出弹窗 2,取消则弹出弹窗 3,弹窗 2 以及 弹窗 3 也存在对应的分支逻辑,子孙满堂轻而易举,若按照常规声明式弹窗组件的实现,非常恐怖!
vue-nice-modal 是一个工具库,可以将 Vue.js 的 modal 组件转换为基于 Promise 的 API。
灵感来源于 @ebay/nice-modal-react 和 vant。
支持 Vue 2.x,通过 vue-demi。
vue@2.7.x 以及 vue@3 已经经过测试,vue@2.6.x 暂未测试,理论上也是支持的(欢迎 PR),建议升级至 vue@2.7.x 享受 composition-api 的特性。
你可以在 examples/* 文件夹中查看完整的项目示例。
npm install vue-nice-modal # or yarn add vue-nice-modal # or pnpm add vue-nice-modal
import { create } from 'vue-nice-modal'; import MyModal from './MyModal.vue'; const myModal = create(MyModal); myModal .show({ title: 'Hello, world!', content: 'This is a nice modal.', }) .then((result) => { console.log('Confirmed! Result:', result); }) .catch((error) => { console.error('Rejected! Error:', error); });
<script setup lang="ts"> import { Dialog } from 'vant'; import { INiceModalHandlers } from 'vue-nice-modal'; // inject hide/remove/callback methods by vue-nice-modal interface IProps extends INiceModalHandlers<number> { visible: boolean; // props you need title: string; content: string; } interface IEmits { (e: 'update:visible', visible: boolean): void; } const props = defineProps<IProps>(); // @ts-ignore const emit = defineEmits<IEmits>(); const handleCancel = () => { props.hide(); // or emit('update:visible', false) props.callback('cancel'); // reject the promise }; const handleConfirm = async () => { // mock async function call const sleep = (ms: number): Promise<number> => new Promise((res) => setTimeout(() => { res(ms); }, ms) ); const payload = await sleep(1000); // resolve the promise with payload props.callback('confirm', payload); }; </script> <template> <dialog :show="visible" @update:show="$emit('update:visible', false)" @cancel="handleCancel" @confirm="handleConfirm" @closed="remove" :title="title" :content="content" show-cancel-button class="demo-dialog" > <div>Hello, Vue Nice Modal</div> </dialog> </template>
完整示例请点击 example-vue3
本节提供了一个使用 vue-nice-modal 库创建自定义 modal 组件的示例。该示例使用 vant UI 库的 Dialog 组件作为示例,但你可以使用任何自定义 modal 组件,并不限制组件库,如在 example-vue2.7.x 里使用的是 ant-design-vue。
很明显,此处与日常工作中自定义一个弹窗组件没有任何分别,只不过通过 INiceModalHandlers 注入了一些通用的方法(示例中除 title 以及 content 的相关内容),你依旧可以通过组件的形式调用该弹窗组件,只不过需要自行注入相关方法以及 visible/update:visible 以实现 v-model 相关绑定。
visible
update:visible
在示例中,visible 属性和 update:visible 事件由 vue-nice-modal 注入到自定义 modal 组件中。这些用于控制 modal 组件的可见性。visible 属性应是一个布尔值,用于确定 modal 是可见的还是不可见,update:visible 事件应在 modal 的可见性改变时触发。
hide()、remove() 和 callback() 方法也由 vue-nice-modal 注入到自定义 modal 组件中。这些方法用于隐藏或删除 modal 组件,以及处理用户确认或取消 modal 操作。
modal 组件创建完毕后,可以使用 vue-nice-modal 提供的 create() 函数来创建一个 Modal 对象,该对象包含 show()、hide() 和 remove() 方法。后续即可使用 show() 方法显示自定义 modal 组件,并使用 vue-nice-modal 提供的基于 Promise 的 API 处理用户确认或取消 modal 操作。
create 函数接受 Vue.js 组件并返回一个带有以下方法的 Modal 对象:
显示 modal 组件并返回一个 Promise,如果用户确认 modal 则 resolve,如果用户取消则 reject。
options 参数是一个对象,包含与 modal 组件相关的属性(除去 vue-nice-modal 注入的通用属性与方法,仅包含用户自定义的所需 props,以达到良好的类型提示)。
以下是 show 方法的类型提示实现:
type ComponentProps<C extends Component> = C extends new (...args: any) => any ? Omit< InstanceType<C>['$props'], keyof VNodeProps | keyof AllowedComponentProps > : never; type ExtractOptions<T extends Record<string, any>> = Omit< T, keyof INiceModalHandlers | 'visible' | 'onUpdate:visible' >; export function create<C extends Component>(Comp: C) { // ... const show = (options: ExtractOptions<ComponentProps<C>>) => { // ... }; return { show, // ... }; }
隐藏 modal 组件。
从 DOM 中删除 modal 组件。
vue-nice-modal 提供了一些 TypeScript 类型定义:
Modal 接口定义了 create 返回的对象的方法。
interface Modal { show: (options: ExtractOptions<ComponentProps<C>>) => Promise<any>; hide: () => void; remove: () => void; }
ComponentProps 工具泛型用于获取 Vue 组件的属性。
type ComponentProps<C extends Component> = C extends new (...args: any) => any ? Omit< InstanceType<C>['$props'], keyof VNodeData | keyof AllowedComponentProps > : never;
INiceModalHandlers 接口定义了用于处理用户确认或取消 modal 的方法。
export interface INiceModalHandlers<T = any> { callback: (action: 'confirm' | 'cancel', payload?: T) => void; remove: () => void; hide: () => void; }
这些方法以及 visible 和 update:visible 事件将被注入用户的自定义 modal 组件中,即使不使用基于 Promise 的函数调用,相关属性也可以通过 v-model(visible 和 update:visible)传递从而控制组件的可见性。这允许用户按自己喜欢的方式控制 modal 组件的显示和隐藏,同时也确保了 vue-nice-modal 库的灵活性。
ExtractOptions 类型用于提取与 modal 组件相关的选项(除去 vue-nice-modal 注入的通用属性与方法)。
type ExtractOptions<T extends Record<string, any>> = Omit< T, keyof INiceModalHandlers | 'visible' | 'onUpdate:visible' >;
前言
笔者去年写过一篇 NiceModal:重新思考 React 中的弹窗使用方式,6 月中旬换了工作,技术栈也由 React + TypeScript 切成了 Vue@2.7 + JavaScript :),但是没有发现 @ebay/nice-modal-react 在 vue 生态中的替代方案,故而有了这篇文章。
如果本文能对你日常开发产生一些新的思考和效率的提升,那就太好了!笔者在工作中接触 vue 的时间也就短短半个月,如有设计不当的地方欢迎 PR 一起改进。 Vue Nice Modal - GitHub
在开始进入正题之前,先看看一些与弹窗有关的有趣场景 🤔。
一些有趣的真实场景
案例一:全局弹窗
上图是掘金的登录弹窗,未登录状态下触发该弹窗展示的时机有很多,比如:
开发者往往会基于第三方组件库定义一个
<LoginModal />
,然后将其挂载至Root
组件下。这样会带来一些问题:
<LoginModal />
内部逻辑在组件渲染的时候就会执行,即使弹窗处于隐藏状态setVisible
以及setOtherLoginData
透传至<Main />
内部的多个子组件,你可以选择通过 props 一层一层的传递进去(鬼知道有多少层!),也可以引入工具进行状态共享,但不论怎样,这都不是一件容易的事;展示一个弹窗,为什么会变得如此复杂?
除了上述全局弹窗的场景,还有一种场景也很让人头疼。
案例二:存在分支以及依赖关系的弹窗
弹窗 1 确认弹出弹窗 2,取消则弹出弹窗 3,弹窗 2 以及 弹窗 3 也存在对应的分支逻辑,子孙满堂轻而易举,若按照常规声明式弹窗组件的实现,非常恐怖!
Vue Nice Modal
vue-nice-modal 是一个工具库,可以将 Vue.js 的 modal 组件转换为基于 Promise 的 API。
灵感来源于 @ebay/nice-modal-react 和 vant。
支持 Vue 2.x,通过 vue-demi。
示例
你可以在 examples/* 文件夹中查看完整的项目示例。
安装
使用
自定义 Modal 组件
本节提供了一个使用 vue-nice-modal 库创建自定义 modal 组件的示例。该示例使用 vant UI 库的 Dialog 组件作为示例,但你可以使用任何自定义 modal 组件,并不限制组件库,如在 example-vue2.7.x 里使用的是 ant-design-vue。
在示例中,visible 属性和 update:visible 事件由 vue-nice-modal 注入到自定义 modal 组件中。这些用于控制 modal 组件的可见性。visible 属性应是一个布尔值,用于确定 modal 是可见的还是不可见,update:visible 事件应在 modal 的可见性改变时触发。
hide()、remove() 和 callback() 方法也由 vue-nice-modal 注入到自定义 modal 组件中。这些方法用于隐藏或删除 modal 组件,以及处理用户确认或取消 modal 操作。
modal 组件创建完毕后,可以使用 vue-nice-modal 提供的 create() 函数来创建一个 Modal 对象,该对象包含 show()、hide() 和 remove() 方法。后续即可使用 show() 方法显示自定义 modal 组件,并使用 vue-nice-modal 提供的基于 Promise 的 API 处理用户确认或取消 modal 操作。
API
create(Comp: Component): Modal
create 函数接受 Vue.js 组件并返回一个带有以下方法的 Modal 对象:
show(options: ExtractOptions<ComponentProps>): Promise
显示 modal 组件并返回一个 Promise,如果用户确认 modal 则 resolve,如果用户取消则 reject。
options 参数是一个对象,包含与 modal 组件相关的属性(除去 vue-nice-modal 注入的通用属性与方法,仅包含用户自定义的所需 props,以达到良好的类型提示)。
以下是 show 方法的类型提示实现:
hide(): void
隐藏 modal 组件。
remove(): void
从 DOM 中删除 modal 组件。
类型定义
vue-nice-modal 提供了一些 TypeScript 类型定义:
Modal
Modal 接口定义了 create 返回的对象的方法。
ComponentProps
ComponentProps 工具泛型用于获取 Vue 组件的属性。
INiceModalHandlers
INiceModalHandlers 接口定义了用于处理用户确认或取消 modal 的方法。
ExtractOptions<T extends Record<string, any>>
ExtractOptions 类型用于提取与 modal 组件相关的选项(除去 vue-nice-modal 注入的通用属性与方法)。
注意
推荐阅读