Vue弹窗类组件的设计与实现

2,600 阅读1分钟

在ElementUI中,除了以<el-form/>这样标签的形式调用组件外,还有以this.$message这样API的形式调用组件,为什么要这么设计呢?理由有二:

  • 在vue中,当使用自定义的非全局组件时,都必须手动import和指定components对象的属性,比较繁琐。以API的形式调用组件,可以享受想用就随时随处能用的快速和便捷。
  • 在vue中,所有页面和组件最终都会以<div id="app"/>为根节点,这个根节点一般我们不会去限制它的css position属性。而对于弹窗这样的组件来说,它一般是在整个页面的中间位置显示,所以,它比较适合挂载在仅次于<body/>的节点上。

1、创建组件实例

为了让弹窗组件挂载在别的根节点上,我们需要手动创建组件实例,这该如何实现呢?且看下面的代码:

<!--create.js-->
// 组件实例的创建
import Vue from 'vue'

function create(Component, props) {
    // 1.创建Vue实例,Component是当前组件实例的根组件
    const vm = new Vue({
        // 1.1 render:虚拟DOM的实现
        // 1.2 h是createElement别名,返回虚拟DOM[VNode]
        // 1.3 参数props是要传递给Component的参数,{props: props, on: {click:onClick}}
        // 比如:<Component title content/>
        render: h => h(Component, { props })
        // 1.4 $mount()会把上面生成的VNode转化成真是DOM,并挂载到目标节点上
        // 若不指定选择器,会执行转化过程,只是不挂载
    }).$mount()

    // 2.手动挂载:使用原生dom api把它插入文档中
    // 2.1 vm.$el:真实dom元素
    document.body.appendChild(vm.$el)

    // 3.回收:防止内存泄漏
    // 3.1 vm.$children[0]获取组件实例
    const comp = vm.$children[0]
    comp.remove = () => {
        document.body.removeChild(vm.$el)
        comp.$destroy()
    }

    return comp
}

export default create

2、将创建组件实例的方法挂载到Vue原型上

//main.js
import Vue from 'vue'
import create from 'create.js'
Vue.prototype.$create = create

3、创建要挂载在组件实例上的弹窗组件

<!--MyPopup.vue-->
<template>
  <div class="my-popup" v-if="isShow">
    <!--弹窗遮罩-->
    <div class="my-popup-bg"></div>
    <!--弹窗容器-->
    <div class="my-popup-ctn">
      <p class="my-popup-ctt">
        <span class="inner-ctt">{{message}}</span>
      </p>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    message: {
      type: String,
      default: ""
    },
    duration: {
      type: Number,
      default: 1000
    }
  },
  data() {
    return {
      isShow: false
    };
  },
  methods: {
    show() {
      this.isShow = true;
      <!--指定一定时间间隔后自动隐藏弹窗-->
      setTimeout(this.hide, this.duration);
    },
    hide() {
      this.isShow = false;
      <!--调用组件实例提供的方法销毁组件实例-->
      this.remove();
    }
  }
};
</script>

<style lang="scss" scoped>
.my-popup {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  .my-popup-bg {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background: rgba($color: #000000, $alpha: 0.4);
  }
  .my-popup-ctn {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 600px;
    padding: 20px;
    transform: translate(-50%, -50%);
    background: rgba($color: #000000, $alpha: 0.8);
    border-radius: 16px;
    .my-popup-ctt {
      display: flex;
      min-height: 100px;
      align-items: center;
      justify-content: center;
      .inner-ctt {
        display: inline-block;
        text-align: left;
        font-size: 30px;
        line-height: 50px;
        color: #fff;
      }
    }
  }
}
</style>

4、已API形式调用自己的弹窗方法

<!--TestMyPopup.vue-->
<template>
  <div>
  </div>
</template>
import MyPopup from "MyPopup.vue";
<script>
  export default {
        mounted () {
            const notice = this.$create(MyPopup, {
                message: '弹~弹~弹,弹走鱼尾纹',
                duration: 2000
            });
            notice.show();
        },
  }
</script>