vue利用渲染函数创建弹窗组件,完美支持传值和事件监听

Posted IT飞牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue利用渲染函数创建弹窗组件,完美支持传值和事件监听相关的知识,希望对你有一定的参考价值。

弹窗组件和其他组件不同,在页面初始化的时候,页面dom结构中是不存在弹窗组件的div节点,只有在调用时弹窗组件的div在会被动态的添加到页面中,并且当删除时,也需要在页面中删除;

结合弹窗组件的使用场景,比较合适的实现是使用createElement渲染函数,渲染函数可以动态的创建组件,并且通过propson属性,传入组件的标题、内容,监听组件的事件;

观察看下面这段代码:

	const vm = new Vue({
            render: h => {
                return h(component, {
                    props,
                    on: Object.assign({}, eventMap[component.name], on)
                });
            }
        }).$mount();

render字段中,h方法就是createElement方法的别名,可以动态创建虚拟Dom节点,h方法传入两个参数,component可以是html标签,也可以是vue组件,这里显然是要传入我们需要显示的alert弹窗组件,第二个参数用于props传值,on用于监听组件内的事件;最终$mount()方法用于生成真实的dom节点。

到这里,大家可以想象一下,整个弹出alert弹窗的过程,需要一下几个角色参与:

  1. 一个通用layer方法,可以挂载在Vue原型上,也可以在使用时手动引入;
  2. 一个alert.vue组件,提供alert页面和样式
  3. 调用弹窗:
    layer(alert.vue,{props:{title:弹窗标题,content:弹窗内容},on:{sure:确定时回调}})

下面是全部代码:

  • /src/components/layer/alert.vue
    onSure和onClose向外emit的方法,在渲染函数中有对应的on:{sure:xxx,close:xxx}来监听;
<template>
  <div v-if="isShow" class="layer mask">
    <div class="layer_alert">
      <div class="layer_alert_title">
        {{ title }}
        <div class="close" @click="onClose">x</div>
      </div>
      <div class="layer_alert_content">{{ content }}</div>
      <div class="layer_alert_button">
        <div class="tbtn sure fr" @click="onSure">确定</div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "alert",
  props: {
    title: {
      type: String,
      default: "提示",
    },
    content: {
      type: String,
      default: "这里是alert提示内容!",
    },
  },
  data() {
    return {
      isShow: false,
    };
  },
  methods: {
    onSure() {
      this.$emit("sure", this);
    },
    onClose() {
      this.$emit("close", this);
    },
    show() {
      this.isShow = true;
    },
    hide() {
      this.isShow = false;
    },
  },
};
</script>

<style>
</style>
  • src\\assets\\css\\layer.css
    弹窗对应的css样式文件,可以在main.js中手动import引入,也可以在webpack中配置entry来自动化导入。
.layer {
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
}

.layer.mask {
    background: rgba(0, 0, 0, 0.3);
}

.layer .layer_alert {
    width: 600px;
    /* height: 450px; */
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background-color: white;
    border-radius: 3px;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
    padding: 10px;
}

.layer .layer_alert .layer_alert_title {
    height: 50px;
    line-height: 50px;
    border-bottom: 1px solid #ddd;
    font-size: 16px;
    position: relative;
}

.layer .layer_alert .layer_alert_title>div.close {
    width: 40px;
    height: 40px;
    text-align: center;
    line-height: 40px;
    position: absolute;
    right: 0;
    top: 5px;
    font-size: 16px;
    cursor: pointer;
}

.layer .layer_alert .layer_alert_content {
    height: 250px;
    padding: 10px 0;
    font-size: 14px;
    line-height: 120%;
}

.layer .layer_alert .layer_alert_button {
    height: 40px;
}
  • /src/utils/layer.js
    这里借助node中的require.context对组件目录做了扫描,@/components/layer中的组件,都会被自动加载,很方便;
    这里仅仅以alert组件为例,其他confirm、popup、modal组件也是一样的架构。最终创建后,返回一个vue实例layer,可以通过layer.$remove()销毁组件在页面上的dom和内存中的存储。
import Vue from "vue";

const modulesFiles = require.context('@/components/layer', true, /\\.vue$/);
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
    // set './app.js' => 'app'
    const moduleName = modulePath.replace(/^\\.\\/(.*)\\.vue$/, '$1');
    const value = modulesFiles(modulePath);
    modules[moduleName] = value.default;
    return modules;
}, {});

export default {
    _create(component, { props, on }) {
        //各类组件的事件监听,component.name是key,必填;
        const eventMap = {
            alert: {
                sure: function (vm) {
                    //vm===返回的layer
                    console.log("sure", vm);
                },
                close: function (vm) {
                    vm.$remove();
                }
            }
        };
        //方案一:渲染函数
        const vm = new Vue({
            render: h => {
                return h(component, {
                    props,
                    on: Object.assign({}, eventMap[component.name], on)
                });
            }
        }).$mount();
        const layer = vm.$children[0];
        layer.$remove = function () {
            document.body.removeChild(layer.$el);
            layer.$destroy();
        }

        document.body.appendChild(layer.$el);
        return layer;

        //方案二:也可使用Vue.extend()来实现弹窗,使用propsData。
    },
    alert: function (option) {
        let layer = this._create(modules.alert, option || {});
        layer.show();
        return layer;
    }
};
  • 调用
    这里直接使用plugin,将layer挂在到了Vue原型上,方便全局调用;plugin的实现可以参考vue插件实现
...
  mounted() {
    this.$layer.alert({
      props: { title: "弹窗标题", content: "弹窗内容" },
      on: {
        close: function (layer) {
          console.log("close 2");
          layer.$remove();
        },
      },
    });
  },
...

最终效果:

以上是关于vue利用渲染函数创建弹窗组件,完美支持传值和事件监听的主要内容,如果未能解决你的问题,请参考以下文章

利用渲染函数,实现动态创建弹窗-Vue3.X

vue 组件传值,传过来的值不能被渲染,怎么办?

30 Vue子组件调用父组件方法传值和校验

Vue:基础语法、创建组件、组件间传值、实例生命周期

vue解决父组件调用子组件只执行一次问题

vue组件父传子、子传父、兄弟组件之间传值