在前面
上一章,我们搞定了Vue官方文档里没有给出的,但在社区都在悄悄用的抽象组件,但是你知道,好东西不止于此...
今天,我们将搞定另一种牛的组件
老规矩,先上目录树
命令式组件
我们将借助Element UI 里的message组件实现命令式组件的基本原理.
-
什么是命令式组件 通过this去调用一个组件;也就是说使用
extend
将组件转为构造函数,在实例化这个这个构造函数后,就会得到$el
属性,也就是组件的真实Dom
,这个时候我们就可以操作得到的真实的Dom
去任意挂载,使用命令式也可以调用。 -
用到的知识点:extend和$mount 官方文档
这两个都是vue
提供的API
,不过在平时的业务开发中使用并不多。在vue
的内部也有使用过这一对API
。遇到嵌套组件时,首先将子组件转为组件形式的VNode
时,会将引入的组件对象使用extend
转为子组件的构造函数,作为VNode
的一个属性Ctor
;然后在将VNode
转为真实的Dom
的时候实例化这个构造函数;最后实例化完成后手动调用$mount
进行挂载,将真实Dom
插入到父节点内完成渲染。
简单理解mount:手动挂载 ,Vue.extend作用:生成一个新的带有默认参数的Vue的子类
所以这个弹窗组件可以这样实现,我们自己对组件对象使用
extend
转为构造函数,然后手动调用$mount
转为真实Dom
,由我们来指定一个父节点让它插入到指定的位置。
- 新建message组件文件夹,然后再main.js导入组件,并use
- 在message/main.vue中,模仿Element ui 写一个message的组件
-
在该组件index.js文件中导入,我们刚刚写好的组件,并生成一个MessageBox构造器,该构造器还应该是一个新的带有默认参数的Vue的子类,带有的默认参数比如就有template ,components
-
那么我们就要思考如何将这个组件渲染到页面上去呢? 我们只需要new这个MessageBox,让它成为一个实例,并执行mount方法触发vue的编译流程,挂载到$el上,然后使用正常的操作dom方法在其父节点中手动替换,然后在main.vue导入,这时候我们的message组件就会正常渲染到页面上,
-
但你要知道,此时虽然能正常渲染到页面,但它的渲染其实实际不受我们的控制,所以,我们需要将刚刚的代码用一个 $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>
- 我们希望,不需要导入,通过this就能调用该函数,则我们就需要,保存Vue实例,注册install方法,然后将将该函数挂载到Vue根实例原型,然后再全局引入,传入use
- 此时如果你看了element ui就会知道,我们的这个函数其实还需要传参
那么,我们又该如何实现呢?
- 我们也在open调用时传入三个参数,在我们定义的messagebox实例化时,在data里返回一个对象时传参进去,并且给默认值,插值表达式修改页面渲染内容
- 需求远远不够....我们看见,此时虽然页面能够正常渲染,但是,取消点不了哇!!☠ ☠ ☠ ☠,在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>
- 其实做到这里我们的组件就已经完成了,但是,佛说要高度还原,所以再看文档,发现是点取消后调用一个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>
最后
熬夜掉头发的感觉真的酸爽
但很多时候还是搞不明白逻辑,记不住,哎!! 这脑壳真差劲
谁来救救我啊!支点妙招