函数式调用:打造高效且灵活的 Vue2 弹窗组件

355 阅读3分钟

引言

在当今的现代前端开发领域,弹窗组件作为关键的交互元素,广泛应用于各类项目中,从简单的提示信息展示到复杂的用户操作确认流程,都离不开它的身影。然而,传统标签的弹窗实现方式往往存在诸多问题。以一个常见的电商项目为例,

  1. 代码重复与维护成本高:多页面使用商品详情弹窗时,需在每个页面重复编写相似的样式和显隐逻辑代码,导致代码量剧增。维护时牵一发而动全身,成本与风险大幅上升。
  2. 耦合度过高,复用性差:传统弹窗与所在页面耦合紧密,显隐依赖页面特定状态变量或方法,难以在其他场景复用,极大限制了代码的扩展性。
  3. 页面逻辑复杂混乱:一个页面需多个弹窗时,各弹窗依赖页面特定状态变量或方法,会使页面内状态与逻辑复杂混乱,增加调试难度,降低代码的可读性与可维护性。

本文将介绍一种基于函数式调用的 Vue 弹窗组件实现方式,通过这种方式,你可以以极高的灵活性和解耦度动态创建和管理弹窗,从而提升开发效率和代码质量。

函数式调用

是一种将组件的创建和管理封装在函数中的方式

  1. 高度解耦:弹窗的创建和销毁不再依赖于具体的页面或组件,而是通过函数调用动态完成。
  2. 灵活复用:同一个弹窗内容组件可以被多次调用,且每次调用都可以传入不同的参数,实现不同的功能。
  3. 易于维护:由于弹窗的逻辑被封装在独立的函数中,修改弹窗的行为只需修改函数。

实现思路

实现一个可以通过函数式调用动态创建和管理的弹窗组件。具体实现思路如下:

  1. 封装弹窗组件:创建一个通用的弹窗组件 DynamicDialog,支持动态传入内容和属性(可以根据项目高度自定义这个组件)。
  2. 解耦逻辑:将弹窗的创建、显示、关闭逻辑封装在独立的类 DialogInstance 中,确保弹窗的管理与页面逻辑完全解耦。
  3. 函数式调用:通过一个函数(可以挂载到 Vue 实例上),动态创建弹窗实例,并传入不同的参数来控制弹窗的行为。

代码实现

const DialogPro = {
  name: "DialogPro",
  template: `
      <el-dialog :visible.sync="visible" v-bind="dialogProps" @close="handleClose('_close')">
        <component ref="componentRef" :is="componentInstance" v-bind="componentProps" @close="handleClose"></component>
      </el-dialog>
    `,
  props: {
    componentInstance: {
      type: [Object, Function]
    },
    componentProps: {
      type: Object
    },
    dialogProps: {
      type: Object
    }
  },
  data() {
    return {
      visible: false
    }
  },
  methods: {
    handleClose(type) {
      this.$emit("close", type);
    }
  }
}

const DialogConstructor = Vue.extend(DialogPro);

class DialogInstance {
  instance = null;
  constructor(options = {}) {
    if (Vue.prototype.$isServer) {
      return;
    }
    this.initInstance(options)
  }
  initInstance(options) {
    // 这里可以指定义处理options的值
    this.instance = new DialogConstructor({
      el: document.createElement("div"),
      propsData: options,
    }).$mount();
    // 监听事件
    this.instance.$on('close', (type) => {
      if (type === "_close") {
        // 点击 X 关闭、 点击 modal 关闭 或者 dialogPro visible 属性变化关闭弹窗,就直接关闭无需处理 options.close 回调;
        this.close();
        return;
      }
      options.close && options.close(type, this.instance.$refs.componentRef, this.close.bind(this));
    });

    const target = options.target || document.body;
    target.appendChild(this.instance.$el);
    Vue.nextTick(()=>{
      this.instance.visible = true;
    })
  }
  close() {
    this.instance.visible = false;
    this.instance.$destroy();
    Vue.nextTick(()=>{
      this.instance.$el.remove();
      this.instance = null;
    })
  }
}

示例

// main.js
import DialogInstance from './components/dialog-func/index.js'
Vue.prototype.$dialog = (options)=>{
  return new DialogInstance(options)
}

// 具体的Vue页面

methods: {
    openDialog() {
      this.$dialog({
        dialogProps: {
          title: "函数式调用dialog",
        },
        // componentInstance: ()=>import("element-ui@2.15.14/lib/input.js")
        componentInstance: {
          template: `
            <div>
            <el-button type="primary" @click="handleClick('confirm')">确定</el-button>
            <el-button @click="handleClick('cancel')">取消</el-button>
            </div>
          `,
          methods: {
            handleClick(type) {
              this.$emit("close", type)
            }
          }
        },
        close: (type, self, done) => {
          console.log(type);
          // self 弹窗内容组件的实例
          done()
        }
      })
    }
  }

后记

通过本文介绍的基于函数式调用的 Vue 弹窗组件实现方式,我们成功解决了传统弹窗实现中代码重复、难以维护、耦合度过高的问题。这种方式以高度解耦的设计,让弹窗逻辑独立于业务页面,大幅增强了代码的稳定性和可扩展性;灵活复用的特性使得同一弹窗组件能够在不同场景发挥多样功能,提升了开发效率;易于维护的优势则降低了开发过程中的维护成本和出错风险。


感谢阅读,敬请斧正!