vue弹出模态框dialog_android各种弹出层

vue弹出模态框dialog_android各种弹出层日常项目开发中,经常需要封装弹出层组件,不同的人会有不同的封装方法,弹出层组件因为涉及到 v-model ,各种事件和显示隐藏的状态管理、数据的流动和父子组件通信,可以设计的很简单,也可以设计的很复杂

日常项目开发中,经常需要封装弹出层组件,不同的人会有不同的封装方法,弹出层组件因为涉及到 v-model ,各种事件和显示隐藏的状态管理、数据的流动和父子组件通信,可以设计的很简单,也可以设计的很复杂,管理不善会给后期维护带来诸多麻烦,这里我总结了一些弹出层的封装方法,比较了优劣,方便以后在处理类似问题上可以很清晰的选择合适的方案。

问题

  • 页面很多弹窗,代码已经上千行,细节太多,维护时容易出错
  • 弹窗的状态和事件应该在哪一层管理?
  • 如何封装弹出层组件?

「导致单文件组件代码过长的几个原因」

  1. 循环列表: 循环项的结构复杂,嵌套多
  2. 没有分组件: 各个独立区域没有封装都在一个页面
  3. Form表单: 表单项多(rules配置项)
  4. Table列表: 列数多,列数要格式化(列配置项)
  5. 弹出层: 弹出层内容结构复杂(多个弹出层未封装)
  6. 业务逻辑没有抽象和分层
  7. 工具类函数和静态数据配置

「如何避免单文件组件的代码长度」

  • 单文件组件代码长度控制在200行左右
  • 弹出层、表单、独立区域、列表都封装成组件 ./components
  • 工具类、配置类单独抽离出来 ./utils ./config.js
  • 相似的代码进行重构
  • 减少嵌套层级,使用 async await
  • 合理的使用Mixin 抽象公共逻辑

弹出层组件的花式用法

状态由父组件管理,只封装Modal 内容

<template>
<a-button type="primary" @click="visible1=true">
  打开Modal1
</a-button>
<a-modal v-model="visible1" @ok="visible1=false">
  <ModalContent />
</a-modal>
</template>

<script> export default {   data() {     return {       visible1false     };   }, } </script>

这种将modal的状态和事件放在了父组件这一层,内容数据处理逻辑放到了组件 ModalContent 里面

这种用法的好处是方便控制Modal的状态,但缺点是Modal不能复用,只能复用内容,父组件中需要管理

modal 的状态和事件。

封装整个Modal,状态由Modal内部管理,watch监听 显示隐藏

<template>
<div>
  <a-button type="primary" @click="visible2=true">
    打开Modal2
  </a-button>
  <TestModal2 :show="visible2" @ok="visible2=false"/>
</div>
</template>

<script> export default {   data() {     return {       visible2false     };   }, } </script>
// TestModal2
<template>
  <a-modal v-model="visible" @click="$emit('ok')">
    <p>{{ form.name || ''}}</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
  </a-modal>
</template>
<script> export default {   props: {     showfalse,   },   data(){     return {       visiblefalse,       form: {         name'张三'       }     }   },   watch: {     show: {       immediatetrue,       handler(v){         this.visible = v;       }     }   } } </script>

子组件需要定义props、需要watch 父组件传递进来的 show 去更新visible

这里不直接使用props 做为modal的v-modal的值,是因为这样会导致数据传递混乱,父子组件都在同时管理show状态,同时也会产生bug,modal关闭后,父组件无法再次打开modal

Modal状态透传

<template>
<div>
  <a-button type="primary" @click="visible3=true">
    打开Modal3
  </a-button>
  <TestModal3 :visible="visible3" @ok="visible3=false"/>
</div>
</template>

<script> export default {   data() {     return {       visible3false     };   }, } </script>
// TestModal3
<template>
  <a-modal v-bind="$attrs" v-on="$listeners">
    <p>{{ form.name || ''}}</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
  </a-modal>
</template>
<script> export default {   data(){     return {       form: {         name'张三'       }     }   }, } </script>

利用子组件根节点绑定 和listeners 可以属性和事件通过父组件透传到子组件,在父组件使用封装的Modal组件时,相当于直接使用 Modal 组件

但缺点也很明显,所有的属性和事件都被父组件接收了,如果要使用modal 组件定义之外的属性和事件,需要从 和listeners 获取,取值比较隐蔽,同时父组件要管理 modal的状态和事件

【推荐用法】使用Ref调用

<template>
<div>
  <a-button type="primary" @click="showModal4">
    打开Modal4
  </a-button>
  <TestModal4 ref="testModal4" @ok="onOk4"/>
</div>
</template>

<script> export default {   methods: {     showModal4() {       this.$refs.testModal4.show({name'李四'});     },     onOk4(data){       console.log(data);       this.$refs.testModal4.close();     }   }, } </script>
// TestModal4
<template>
  <a-modal v-model="visible">
    <p>{{ form.name || ''}}</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
    <p>Some contents...</p>
    <a-button type="primary" @click="onOk">确认</a-button>
  </a-modal>
</template>
<script> export default {   data(){     return {       visiblefalse,       form: {         name'张三'       }     }   },   methods: {     show(initData){       this.visible = true;       if(initData) {         this.form = {...initData }       }else{         this.form = { name'张三' }       }     },     close(){       this.visible = false;     },     onOk(){       this.$emit('ok'this.form);     }   } } </script>

这里父组件利用refs 来调用子组件的方法,Modal 所有状态和逻辑都由Modal内部管理,父组件使用Modal组件时,只需要通过 refs 来调用即可,不需要通过prop 传递,使用更加灵活,通过 调用 refs.modal.show() 可以传递相关数据给子组件进行初始化,子组件复用性也比较好,可以在任意其它业务中复用。

使用JS函数式直接调用

在基于 refs 封装的基础之上,可再封装成函数式方法调用,不用再引用组件,直接使用函数调用,类型 组件库中常见的Message.show({}), this.$dialog.show() 直接显示一个弹出层组件

import Vue from 'vue';
import EditUserModal from './index.vue';

const createInstance = (options = {}) => {
  const div = document.createElement('div');
  const el = document.createElement('div');
  div.appendChild(el);
  document.body.appendChild(div);

  const modalProps = { props: {}, ref'editUserModal' };

  let instance = null;
  const refName = modalProps.ref;
  const { data = {}, isEdit = false, onOk, onCancel, onDelete, onAfterclose, ...otherProps } = options;

  const disdroy = () => {
    if (!instance) return;
    instance.$destroy();
    instance = null;
    div.parentNode.removeChild(div);
  };

  const render = (props) => {
    const handleAfterClose = () => {
      onAfterclose && onAfterclose();
      disdroy();
    };

    const modalProps = {
      props: otherProps,
      on: {
        cancel() => {
          onCancel && onCancel();
        },
        ok(data) => {
          onOk && onOk(data);
        },
        delete() => {
          onDelete && onDelete();
        },
      },
    };

    return new Vue({
      el,
      render() {
        return <EditUserModal {...modalPropsref={refName} onAfterclose={handleAfterClose} />;
      },
    });
  };

  instance = render(otherProps);
  instance.$refs[refName].show(data, isEdit);

  return instance;
};

/**  * 函数式调用 编辑用户弹出层  * @param {*} options.onCancel  * @param {*} options.onOk  * @param {*} options.onDelete  * @param {*} options.onAfterclose  * @param {*} options.data  * @param {*} options.isEdit  * @example editUserModal.show({ onOk: (data) => {}, title: ’编辑用户'})  */
const show = (options) => createInstance(options);

export const editUserModal = { show };

这里的封装逻辑基本也是通用的,在组件库开发中是非常有用的,可以简单开发者的调用方式,在平常的业务开发中,封装这样的方式有一定的成本,如果是跨业务十分通用的弹出层组件可以这样封装。

这种封装要点是要处理好弹出层关闭后的逻辑,必须销毁掉实例,并从真实节点中移除,另外也是处理好show方法的传参问题,是否可以覆盖到组件的api。

弹出层通用逻辑封装为Mixin

利用Vue2 的 mixin 可以实现类似继承的特性,很方便复用一些逻辑

/**  * modal.mixin.js  * 通用弹出层逻辑封装  * 用于编辑或新增弹出层,也可用于普通展示弹出层  */
import { cloneDeep } from 'lodash-es';

export default {
  props: {
    width: {
      typeNumber,
      default520,
    },
    title: {
      typeString,
      default'编辑',
    },
  },
  data() {
    return {
      // 是否为编辑
      isEditfalse,
      // 隐藏/显示
      visiblefalse,
      // 用于重置表单
      defaultForm: {},
      // 用于存储表单数据,双向绑定
      form: {},
    };
  },
  methods: {
    // ------------------------- 对外接口(可覆盖,可通过ref调用)
    // 显示
    show(data, isEdit = false) {
      this.isEdit = isEdit;
      this.visible = true;
      this.form = this.formatEditData(cloneDeep({ ...this.defaultForm, ...data }));
    },
    // 关闭
    close() {
      this.visible = false;
    },
    // 处理提交时的数据 [外部重写]
    formatSubmitData(data) {
      return data;
    },
    // ------------------------- 私有方法
    // 关闭之后 重置表单
    _handleAfterClose() {
      this.$refs.modalForm && this.$refs.modalForm.resetFields();
      this.$emit('afterclose');
    },
    // 取消/关闭
    _handleCancel() {
      this.$emit('cancel');
      this.close();
    },
    _handleDelete() {
      this.$emit('delete');
      this.close();
    },
    // 确认
    _handleOk() {
      // 兼容非表单弹出层
      if (!this.$refs.modalForm) {
        this.$emit('ok');
        this.close();
        return;
      }
      // 表单不自动关闭,需要手动关闭
      this.$refs.modalForm.validate((v) => {
        if (!v) return;

        // 提交之前做些处理
        const formData = cloneDeep(this.form);
        const data = this.formatSubmitData(formData);
        this.$emit('ok', data);
      });
    },
  },
};

这个 mixin 可以用于普通的弹窗和表单弹窗,内置了显示隐藏的处理逻辑,在实现层只需要关心业务逻辑,也可以覆盖相关方法来扩展相关Modal的逻辑。

小结

弹出层组件的合理封装有利于代码的后期维护,也方便在业务中复用相关逻辑,不同的场景有不同的封装方式,这里我推荐在业务开发中尽量使用 refs 的封装方式,逻辑更聚焦,与父组件耦合度低,复用性强,也能进一步抽象出mixin,进一步复用,另外在组件库开发中,尽量提供函数式调用的方式,可以简化使用者的调用方式。

Demo源码 Github(vite+vue2.x)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13843.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注