Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue Nice Modal:让你的 Modal 调用更轻松 #18

Open
worldzhao opened this issue Jul 2, 2023 · 0 comments
Open

Vue Nice Modal:让你的 Modal 调用更轻松 #18

worldzhao opened this issue Jul 2, 2023 · 0 comments

Comments

@worldzhao
Copy link
Owner

前言

笔者去年写过一篇 NiceModal:重新思考 React 中的弹窗使用方式,6 月中旬换了工作,技术栈也由 React + TypeScript 切成了 [email protected] + JavaScript :),但是没有发现 @ebay/nice-modal-react 在 vue 生态中的替代方案,故而有了这篇文章。

对于 React 技术栈写过一篇 2021-2022,我的前端最佳实践,有兴趣的同学可以参考阅读。

如果本文能对你日常开发产生一些新的思考和效率的提升,那就太好了!笔者在工作中接触 vue 的时间也就短短半个月,如有设计不当的地方欢迎 PR 一起改进。 Vue Nice Modal - GitHub

在开始进入正题之前,先看看一些与弹窗有关的有趣场景 🤔。

一些有趣的真实场景

案例一:全局弹窗

image

上图是掘金的登录弹窗,未登录状态下触发该弹窗展示的时机有很多,比如:

  1. 点击 Header 上的登录按钮
  2. 文章列表页或详情页点赞以及评论文章
  3. 发沸点、评论沸点以及点赞沸点
  4. ...

开发者往往会基于第三方组件库定义一个 <LoginModal />,然后将其挂载至 Root 组件下。

这样会带来一些问题:

  1. <LoginModal /> 内部逻辑在组件渲染的时候就会执行,即使弹窗处于隐藏状态
  2. 额外的复杂度。由于存在多个触发时机,需要将 setVisible 以及 setOtherLoginData 透传至 <Main /> 内部的多个子组件,你可以选择通过 props 一层一层的传递进去(鬼知道有多少层!),也可以引入工具进行状态共享,但不论怎样,这都不是一件容易的事;
  3. 随着类似的弹窗越来越多,根组件会维护很多弹窗组件的状态...天知道为什么展示一个弹窗需要在多个文件里反复横跳。

展示一个弹窗,为什么会变得如此复杂?

以上案例来自 @ebay/nice-modal-react,稍作修改

除了上述全局弹窗的场景,还有一种场景也很让人头疼。

案例二:存在分支以及依赖关系的弹窗

image

image

用户头疼,开发者也头疼

弹窗 1 确认弹出弹窗 2,取消则弹出弹窗 3,弹窗 2 以及 弹窗 3 也存在对应的分支逻辑,子孙满堂轻而易举,若按照常规声明式弹窗组件的实现,非常恐怖!

image

Vue Nice Modal

vue-nice-modal 是一个工具库,可以将 Vue.js 的 modal 组件转换为基于 Promise 的 API。

灵感来源于 @ebay/nice-modal-reactvant

支持 Vue 2.x,通过 vue-demi

[email protected] 以及 vue@3 已经经过测试,[email protected] 暂未测试,理论上也是支持的(欢迎 PR),建议升级至 [email protected] 享受 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);
  });

自定义 Modal 组件

<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 事件由 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 方法的类型提示实现:

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,
    // ...
  };
}

hide(): void

隐藏 modal 组件。

remove(): void

从 DOM 中删除 modal 组件。

类型定义

vue-nice-modal 提供了一些 TypeScript 类型定义:

Modal

Modal 接口定义了 create 返回的对象的方法。

interface Modal {
  show: (options: ExtractOptions<ComponentProps<C>>) => Promise<any>;
  hide: () => void;
  remove: () => void;
}

ComponentProps

ComponentProps 工具泛型用于获取 Vue 组件的属性。

type ComponentProps<C extends Component> = C extends new (...args: any) => any
  ? Omit<
      InstanceType<C>['$props']
      keyof VNodeData | keyof AllowedComponentProps
    >
  : never;

INiceModalHandlers

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<T extends Record<string, any>>

ExtractOptions 类型用于提取与 modal 组件相关的选项(除去 vue-nice-modal 注入的通用属性与方法)。

type ExtractOptions<T extends Record<string, any>> = Omit<
  T,
  keyof INiceModalHandlers | 'visible' | 'onUpdate:visible'
>;

注意

  • modal 组件必须具有 visible 属性和 update:visible 事件以控制其可见性。请参阅 MyModal.vue 作为示例。
  • vue-nice-modal 会在 DOM 中添加一个根元素 div.vue-nice-modal-root。请确保样式兼容。

推荐阅读

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant