Vue@2.X --- 官方文档上没有的命令式组件(借助$mount和extend实现)

474 阅读3分钟

在前面

上一章,我们搞定了Vue官方文档里没有给出的,但在社区都在悄悄用的抽象组件,但是你知道,好东西不止于此...

今天,我们将搞定另一种牛的组件

老规矩,先上目录树

image.png

命令式组件

我们将借助Element UI 里的message组件实现命令式组件的基本原理.

  1. 什么是命令式组件 通过this去调用一个组件;也就是说使用extend将组件转为构造函数,在实例化这个这个构造函数后,就会得到$el属性,也就是组件的真实Dom,这个时候我们就可以操作得到的真实的Dom去任意挂载,使用命令式也可以调用。

  2. 用到的知识点:extend和$mount 官方文档

这两个都是vue提供的API,不过在平时的业务开发中使用并不多。在vue的内部也有使用过这一对API。遇到嵌套组件时,首先将子组件转为组件形式的VNode时,会将引入的组件对象使用extend转为子组件的构造函数,作为VNode的一个属性Ctor;然后在将VNode转为真实的Dom的时候实例化这个构造函数;最后实例化完成后手动调用$mount进行挂载,将真实Dom插入到父节点内完成渲染。 简单理解mount:手动挂载 ,Vue.extend作用:生成一个新的带有默认参数的Vue的子类

所以这个弹窗组件可以这样实现,我们自己对组件对象使用extend转为构造函数,然后手动调用$mount转为真实Dom,由我们来指定一个父节点让它插入到指定的位置。

  1. 新建message组件文件夹,然后再main.js导入组件,并use

image.png

  1. message/main.vue中,模仿Element ui 写一个message的组件
<template>
  <div class="cl-message-box">
    <div class="cl-message-box__header">
      <div class="el-message-box__title">
        <!----><span>提示</span>
      </div>
    </div>
    <div class="cl-message-box__content">此操作将永久删除该文件,是否确认删除</div>
    <div class="cl-message-box__btns">
      <Button>取消</Button>
      <Button type="primary">确定</Button>
    </div>
  </div>
</template>

<script>
import Button from '@/components/Button/main.vue';
export default {
  name: 'cl-message-box',
  components: { Button }
};
</script>

<style lang="scss" scoped>
.cl-message-box {
  display: inline-block;
  width: 420px;
  padding-bottom: 10px;
  vertical-align: middle;
  background-color: #fff;
  border-radius: 4px;
  border: 1px solid #ebeef5;
  font-size: 18px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  text-align: left;
  overflow: hidden;
  backface-visibility: hidden;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -120%);
  &__header {
    position: relative;
    padding: 15px 15px 10px;
  }
  &__content {
    padding: 10px 15px;
    color: #606266;
    font-size: 14px;
  }
  &__btns {
    padding: 5px 15px 0;
    text-align: right;
  }
  &__content {
    padding: 10px 15px;
    color: #606266;
    font-size: 14px;
  }
  &__title {
    padding-left: 0;
    margin-bottom: 0;
    font-size: 18px;
    line-height: 1;
    color: #303133;
  }
}
</style>
  1. 在该组件index.js文件中导入,我们刚刚写好的组件,并生成一个MessageBox构造器,该构造器还应该是一个新的带有默认参数的Vue的子类,带有的默认参数比如就有template ,components

  2. 那么我们就要思考如何将这个组件渲染到页面上去呢? 我们只需要new这个MessageBox,让它成为一个实例,并执行mount方法触发vue的编译流程,挂载到$el上,然后使用正常的操作dom方法在其父节点中手动替换,然后在main.vue导入,这时候我们的message组件就会正常渲染到页面上,

  3. 但你要知道,此时虽然能正常渲染到页面,但它的渲染其实实际不受我们的控制,所以,我们需要将刚刚的代码用一个 $message函数包起来,然后在页面调用这个函数的执行,此时,我们的命令式组件的基本逻辑就已经实现啦,通过调用这个函数,让其组件在页面渲染


import Vue from 'vue/dist/vue';
import Main from './main.vue';


function $message() {
    // 实例化MessageBox组件
    const messagebox = new MessageBox();
    // 调用$mount()不传递参数 触发模板编译流程
    messagebox.$mount();
    // 通过messagebox实例的$el属性获取到编译后的结果 手动添加到页面中
    document.body.appendChild(messagebox.$el);
  });
}

//导入函数
export default $message;

<template>
  <div>
    <cl-button @click.native="open" type="primary">message box</cl-button>
  </div>
</template>

<script>
//导入
import { $message } from '../components/MessageBox';
export default {
  methods: {
    open() {
    //调用
     $message();
    },
  },
};
</script>

<style>
</style>
  1. 我们希望,不需要导入,通过this就能调用该函数,则我们就需要,保存Vue实例,注册install方法,然后将将该函数挂载到Vue根实例原型,然后再全局引入,传入use
  2. 此时如果你看了element ui就会知道,我们的这个函数其实还需要传参

image.png 那么,我们又该如何实现呢?

  1. 我们也在open调用时传入三个参数,在我们定义的messagebox实例化时,在data里返回一个对象时传参进去,并且给默认值,插值表达式修改页面渲染内容
  2. 需求远远不够....我们看见,此时虽然页面能够正常渲染,但是,取消点不了哇!!☠ ☠ ☠ ☠,在index.jsdata设置标记变量messageboxVisiable:true,利v-if绑定标记变量 给按钮绑定点击事件,修改标记变量,此时就可以点击按钮控制messagebox的显示隐藏了

完善代码


// import Vue from 'vue/dist/vue';
import Main from './main.vue';
let Vue = null;
function message(
  message = '我是一条消息!',
  title = '标题',
  options = {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'info',
  }
) {
    // MessageBox是Vue类的子类 带有一些默认参数 比如template components
    const MessageBox = Vue.extend(Main);
    // 实例化MessageBox组件
    const messagebox = new MessageBox({
      //
      data() {
        return {
          message,
          title,
          options,
          //标记变量
          messageBoxVisible: true,
        };
      },
     
    // 调用$mount()不传递参数 触发模板编译流程
    messagebox.$mount();
    // 通过messagebox实例的$el属性获取到编译后的结果 手动添加到页面中
    document.body.appendChild(messagebox.$el);
  });
}

message.install = function (_Vue) {
  Vue = _Vue;
  _Vue.prototype.$message = this;
};

export default message;

<template>
  <div class="cl-message-box" v-if="messageBoxVisible">
    <div class="cl-message-box__header">
      <div class="el-message-box__title">
        <!----><span>{{ title }}</span>
      </div>
    </div>
    <div class="cl-message-box__content">{{ message }}</div>
    <div class="cl-message-box__btns">
      <Button @click.native="hide">{{ options.confirmButtonText }}</Button>
      <Button @click.native="hide" type="primary">{{
        options.cancelButtonText
      }}</Button>
    </div>
  </div>
</template> 

<script>
import Button from '@/components/Button/main.vue';
export default {
  name: 'cl-message-box',
  components: { Button },

  methods: {
    hide() {
      this.messageBoxVisible = false;
      //优化,实例模板消失后,实例销毁
      this.$nextTick(() => this.$destroy());
    },
   
  },
};
</script>

  1. 其实做到这里我们的组件就已经完成了,但是,佛说要高度还原,所以再看文档,发现是点取消后调用一个catch方法,确定调用then方法,分析得出,返回了Promise对象,于是继续改造呗!!!

得到组件最终版

import Main from './main.vue';
let Vue = null;
function message(
  message = '我是一条消息!',
  title = '标题',
  options = {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'info',
  }
) {
  return new Promise((resolve, reject) => {
    // MessageBox是Vue类的子类 带有一些默认参数 比如template components
    const MessageBox = Vue.extend(Main);
    // 实例化MessageBox组件
    const messagebox = new MessageBox({
      //
      data() {
        return {
          message,
          title,
          options,
          messageBoxVisible: true,
        };
      },
      methods: {
        resolve,
        reject,
      },
    });
    // 调用$mount()不传递参数 触发模板编译流程
    messagebox.$mount();
    // 通过messagebox实例的$el属性获取到编译后的结果 手动添加到页面中
    document.body.appendChild(messagebox.$el);
  });
}

message.install = function (_Vue) {
  Vue = _Vue;
  //挂载到原型
  _Vue.prototype.$message = this;
};
//导出
export default message;

<template>
  <div class="cl-message-box" v-if="messageBoxVisible">
    <div class="cl-message-box__header">
      <div class="el-message-box__title">
        <!----><span>{{ title }}</span>
      </div>
    </div>
    <div class="cl-message-box__content">{{ message }}</div>
    <div class="cl-message-box__btns">
      <Button @click.native="cancal">{{ options.confirmButtonText }}</Button>
      <Button @click.native="confirm" type="primary">{{
        options.cancelButtonText
      }}</Button>
    </div>
  </div>
</template> 

<script>
import Button from '@/components/Button/main.vue';
export default {
  name: 'cl-message-box',
  components: { Button },

  methods: {
    hide() {
      this.messageBoxVisible = false;
      //优化,实例模板消失后,实例销毁
      this.$nextTick(() => this.$destroy());
    },
    调用时
    cancal() {
      this.reject();
      this.hide();
    },
    //成功时
    confirm() {
      this.resolve();
      this.hide();
    },
  },
};
</script>


最后

熬夜掉头发的感觉真的酸爽

但很多时候还是搞不明白逻辑,记不住,哎!! 这脑壳真差劲

谁来救救我啊!支点妙招