vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Modal弹窗封装命令式与Hooks用法

基于Ant-design-vue的Modal弹窗 封装 命令式与Hooks用法

作者:JiangHong

这篇文章主要给大家介绍了基于Ant-design-vue的Modal弹窗封装命令式与Hooks用法,文中有详细的代码示例,具有一定的参考价值,感兴趣的同学可以借鉴阅读

前言

通常大家在使用弹窗有多样化的使用方式,常见的是直接使用该 Modal 组件,然后显隐的状态放在父容器里面维护。

其次就是在全局挂载一个公共的弹窗组件,然后通过 store 来传递不同的参数,并且通过 store 中的方法来改变 state.visble 的状态,从而使得弹窗展示。

虽然说这些种方式可以实现同等功能,但总觉得用的不是很直接, 逻辑片段写的也很分散, 后期代码量越多维护成本越高,那有没有一种比较好的思路处理这个问题呢?将逻片段进行统一管理。

下面我逐一说一下正常开发中 model 弹窗的组件和我自己研究出来的使用方式,在 命令式hooks 两种方式中可以完全的解决代码逻辑分散问题。

组件式-用法

使用方式

<template>
  <Modal v-model:visible="visible" title="弹窗"> ...content </Modal>
  <Button type="primary" @click="visible = true">打开弹窗</Button>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { Modal, Button } from "ant-design-vue";
export default defineComponent({
  components: {
    Modal,
    Button,
  },
  setup() {
    const visible = ref(false);
    return { visible };
  },
});
</script>

组件式用法也可以说是传统用法实际就是把市面上一些封装好的ui组件导入进来使用,或者是自己封装的组件。这样使用确实很方便也很快捷。

比如说现在有一个需求,在当前页面中有 2 按钮需要打开 2 个不同的弹窗。此时我们需要在当前组件中声明 2 个 visible 初始值为 ref(false) 的变量,这还仅仅是控制一个显隐的功能,那么随着业务的复杂度增加,当前页面需要打开弹窗的按钮增加,那么当前组件就会变得越来越复杂,维护成本随之也会增高。很有可能一个逻辑清晰的组件就会变得如下代码一样。

<template>
  <Modal v-model:visible="editVisible" title="弹窗">
    <Form>
      <FormItem label="名称">
        <Input v-memo="editFormData.name" placeholder="请输入" />
      </FormItem>
    </Form>
  </Modal>
  <Button type="primary" @click="editVisible = true">修改数据</Button>
  <Modal v-model:visible="detailVisible" title="弹窗"> ...detailContent </Modal>
  <Button type="primary" @click="detailVisible = true">查看详情</Button>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
import { Modal, Button, Form, FormItem, Input } from "ant-design-vue";
export default defineComponent({
  components: {
    Modal,
    Button,
    Form,
    FormItem,
    Input,
  },
  setup() {
    const editVisible = ref(false);
    const detailVisible = ref(false);
    const editFormData = reactive({ name: "" });
    return { editVisible, detailVisible, editFormData };
  },
});
</script>

随随便便增加一点业务逻辑两个弹窗组件的数据就会混在一起,当然我们可以把弹窗渲染的一部分内容抽离出来,这样也不能排除你增加按钮的逻辑。

命令式-用法

使用方式

<template>
  <Button @click="openAModal"></Button>
</template>
<script>
import { Button } from "ant-design-vue";
import openAModal from "./openAModal";
export default defineComponent({
  components: {
    Button,
  },
  setup() {
    return {
      openAModal,
    };
  },
});
</script>

通过以上代码,可以看出命令式使用方式要比组件式 使用方式简单的多,直接调用一个方法就可以实现同等等功能,非常的快捷方便,后期需要修改业务路基即可一针见血的直接奔向这个方法来做修改就好,相比组件式用法也更易于维护。

使用命令式用法,我们需要创建一个工具函数来包装一下 Modal。下面的代码是针对 ant-design-vue@2.x 做的封装这部分的逻辑我就不分析了,主要是看如何使用这个命令式的调用方法,感兴趣的同仁们,可以自行研究。当然下面代码也针对于 antModal 特殊优化了一下,用过的人可能知道 使用Ant Modal如果对 onOk 事件不做处理的话他是无法主动关闭弹窗的。而且 ant ModalokText、cancelText 一定要给一个名称 不然他默认第一次显示的是中文,关闭后再次打开却是英文。

核心实现逻辑

// modal.js
import { createVNode, render as vueRender } from "vue";
import { Modal as AntModal } from "ant-design-vue";
/**
 * @param {import("vue").DefineComponent} content
 * @param {import("vue").Prop} props
 * @param {Omit<import("ant-design-vue").ModalProps, 'visible'>} config
 */
export default function modal(content, props, config) {
  const container = document.createDocumentFragment();
  const _contentVnode = createVNode(content, props);
  const metadata = Object.create({
    okText: "确定",
    cancelText: "取消",
    visible: true,
    ...config,
  });
  metadata.onCancel = async function (...arg) {
    await config.onCancel?.(...arg);
    close();
  };
  metadata.onOk = async function () {
    if (!(config.onOk instanceof Function)) {
      close();
      return;
    }
    const result = config.onOk();
    if (!(result instanceof Promise)) {
      close();
      return;
    }
    update({ confirmLoading: true });
    return result
      .then(() => {
        update({ confirmLoading: false });
        close();
      })
      .catch(() => {
        update({ confirmLoading: false });
      });
  };
  let vm = createVNode(AntModal, metadata, () => _contentVnode);
  /**
   *
   * @param {typeof config} config
   */
  function update(config) {
    Object.assign(vm.component.props, config);
    vm.component.update();
  }
  function close() {
    metadata.visible = false;
    update(metadata);
  }
  function destroy() {
    if (vm) {
      vueRender(null, container);
      vm = null;
    }
  }
  /** 渲染 */
  vueRender(vm, container);
  return {
    ..._contentVnode,
    close,
    destroy,
    update,
  };
}

主要工具实现完了后,现在假设有一个 A.vue 的组件,需要在 modal 弹窗中渲染,并且支持一个可配的 title ,通常我们的写法是直接在 template中写入 <Modal> <A title="名称" /> </Modal>,但这次我们不这么做,我们先写一个 A.vue的组件 代码如下:

// A.vue
<template>
  <h1>我是A组件</h1>
  <div>{{ title }}</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
  props: {
    title: {
      type: String,
      default: "",
    },
  },
});
</script>

现在基于 A.vue 来声明一个调用弹窗的方法,因为没有业务场景 所以下面的方法简单点没有去设置形参,如果后面业务有需求可以传递参数来使用。代码如下:

// openAModal.js
import A from './A.vue'
import modal from './modal.js'
export default function openAModal() {
    modal(A, {title: 'A组件的props'}, {...弹窗的配置项})
}

后面使用直接调用 openAModal 即可在弹窗中展现 A.vue。这样在使用的时候无需在意 template 结构,更不用去声明 visible 的属性,弹窗的逻辑也不会与业务混合在一起,后期也方便维护,完全符合设计原则中的单一职责

当然这样也会有缺陷,因为弹窗组件是使用 render 函数渲染的,所以在modal 中的组件无法获取到 vue 实例中全局注册的一些方法,比如你全局 app.use(router),那么你在 A.vue 中是无法通过 useRouter() 来获取路由实例。

通常这样打开弹窗,组件内使用的参数建议以传递参数的形式,不要全局获取。 如果你非要在组件中使用 useRouteruseStore 等一些全局注册的工具,请继续阅读下面的 hooks 用法,该方法模拟了 arco-design-reactuseModal 用法,相对命令式封装做的事情要少的多。

Hooks-用法

使用方式

<template>
  <component :is="contextHolder" />
  <Button @click="openAModal">打开弹窗</Button>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { Button } from "ant-design-vue";
import useModal from "./useModal.js";
import A from "./A.vue";
export default defineComponent({
  components: {
    Button,
  },
  setup() {
    const [modal, contextHolder] = useModal({ title: "弹窗", content: A });
    return {
      openAModal: modal.show,
      contextHolder,
    };
  },
});
</script>

通过以上使用示例,使用方式类似于命令式,也是调用api去操作弹窗。但是比 命令式 多了一个 contextHolder,为了处理 modal 不脱离当前 vue 的实例,直接借助于vue component 组件去渲染当前我们自己定义的 Component。这样一来完美的解决了 命令式 调用方法的缺陷问题。

hook 的使用方式,应该也可说是一种高级封装组件的方式,需要传递一个你要渲染的组件然后,暴露一个 contextHolder 和一些 Api 即可, 然后操作当前hooks的 Api 来控制 modal

核心实现逻辑

// useModal
import { createVNode, ref, defineComponent } from "vue";
import { Modal as AntModal } from "ant-design-vue";
/**
 * @typedef {ReturnType<typeof defineComponent>} Component
 * @typedef {Omit<import("ant-design-vue").ModalProps, 'visible'>} ModalProps
 * @param {Omit<import("ant-design-vue").ModalProps, 'visible'> & {content: Component}} props
 *
 * @returns {[{show:() => void}, Component]}
 */
export default function useModal(props = {}) {
  const visible = ref(false);
  // 定义需要渲染的组件即Modal
  const contextHolder = defineComponent({
    render() {
      return createVNode(AntModal, { ...props, visible: visible.value }, () =>
        createVNode(props.content)
      );
    },
  });
  const show = () => {
    visible.value = true;
  };
  return [{ show }, contextHolder];
}
}

上面简单的实现了一个 useModal ,使用 jsDoc 定义了一下入参和返回参数,可以看到出来入参在基于 modalprops基础之上增加了一个content 属性,就是用来接受你要渲染不同的组件的。

useModal 简单的暴露了一个 show 方法,还有一些方法大家都可以自由发挥的,比如说,传入的组件怎么怎么传参数,传入的组件props变更怎么去更新,点击 onOk 的时候怎么获取到传入组件的里面的数据。这些都是可以通过方法去实现的,一些边界问题我就没有去细处理了。主要能够了解这种写法的思路。有思路之后,就可以随意改造。并且还能保留原有的类型提示。让你在使用与排错的时候不会迷路。

总结

相对于正常开发我们使用了 vuerender 、component 的 is 、createVnode,借助了 arco-design-react 封装的 useModalant-design-vueModal.confirm 思路来自动动手改造了一版,vue 版本的 useModal命令式 的使用方法。当然如果能够在vue中熟练使用 jsx 语法, 那基于这个封装简直是再简单不过了。

最后感谢大家的阅读,希望本文对你在前端开发的学习和实践中有所帮助。继续保持好奇心,追求卓越,享受前端开发的旅程!

以上就是基于Ant-design-vue的Modal弹窗封装命令式与Hooks用法的详细内容,更多关于Ant-design-vue弹窗封装与Hooks用法的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文