vue.extend()方法其实是vue的一个构造器,继承自vue。
知道了它的涵义,怎么应用更是我们所关注和关心的问题。
因此对extend它的应用,这里着重从3个方面去介绍。
mint-ui
中的应用;vue工程
中程序实现;extend
的主要应用方向;mint-ui
中的应用开源组件库中Toast[./example/pages/toast/]
// toast/src/toast.js
import Vue from 'vue';
// 强调一下:我们使用是require('./toast.vue')应该换成es6方式 import toast from './toast.vue' , 否则会返现创建子类el非dom对象类型,而是comment
/**
正确的引入方式:
import toast from './toast'
const ToastConstructor = Vue.extend(toast);
*/
const ToastConstructor = Vue.extend(require('./toast.vue')); /** extend,vue构造器,创建‘./toast.vue’ 的子类 */
let toastPool = [];
let getAnInstance = () => {
/** 创建实例,如果toastPool中有实例则不再重复创建。起到单例效果 */
if (toastPool.length > 0) {
let instance = toastPool[0];
toastPool.splice(0, 1);
return instance;
}
return new ToastConstructor({
el: document.createElement('div') /** 通过el,操作dom。给toast外层创建一个 */
});
};
let returnAnInstance = instance => {
if (instance) {
toastPool.push(instance); /** toast实例装箱缓存 */
}
};
let removeDom = event => {
/** 操作dom,用作移除,通过上面创建的放入某页面中的toast.vue */
if (event.target.parentNode) {
event.target.parentNode.removeChild(event.target);
}
};
/** 关闭弹窗close方法 */
ToastConstructor.prototype.close = function() {
this.visible = false;
this.$el.addEventListener('transitionend', removeDom);
this.closed = true;
returnAnInstance(this);
};
/** 调起弹窗的方法,使用时直接通过方法调用即可。如: Toast(options) */
let Toast = (options = {
}) => {
let duration = options.duration || 3000;
let instance = getAnInstance(); // 获取vue实例
instance.closed = false; // instance对象参数close。用以toast.vue中变量的动态赋值,控制弹窗的关闭
clearTimeout(instance.timer);
instance.message = typeof options === 'string' ? options : options.message; // instance对象参数message 。用以toast.vue中变量的动态赋值 ,弹窗显示的提示文字
instance.position = options.position || 'middle'; // instance对象参数position 。用以toast.vue中变量的动态赋值,弹窗显示的页面位置
instance.className = options.className || ''; // instance对象参数className。用以toast.vue中变量的动态赋值 ,弹窗样式控制
instance.iconClass = options.iconClass || ''; // instance对象参数iconClass 。用以toast.vue中变量的动态赋值 ,弹窗样式中是否有图标
document.body.appendChild(instance.$el); // 通过①创建的外层,将toast的整个dom添加入body中
// 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
Vue.nextTick(function() {
/** toast update之后,回调 */
instance.visible = true;
instance.$el.removeEventListener('transitionend', removeDom); // 从页面中移除toast
~duration && (instance.timer = setTimeout(function() {
// 定时器,自动关闭弹窗
if (instance.closed) return;
instance.close();
}, duration));
});
return instance;
};
export default Toast;
从源码中注释中,我们大概能对extend使用上知道个原委。其实就是通过vue.extend
对toast.vue
创建出一个dom子类
。且该dom字类f封装之后是自由自在、随取随用的。若某页面放入弹窗,即该dom子类将通过代码document.body.appendChild(instance.$el);
以绝对定位方式加入到页面中,也就是最初的哪个html(~/public/index.html)
中。
其中这里要重点介绍的是一个toast的vue组件,它的view模板的样式是怎么控制,进行动态的个性化设置的?看下面templete模板的截图:
通过在脚本
中豫定义了props和data,在该.vue通过extend创建的字类,并通过new命令创建出的实例instance来对props和data中绑定的变量进行赋值。从而实现自定义的动态样式。
源代码应用到工程中
这里应用到工程中,是将mint-ui组件库中的源码拷贝出来,粘贴到本地工程中。这里讲述局部配置
将组件库mint-ui中的自定义组件toast程序,直接拷贝到自己的工程目录中。但是在使用的时候,还是需要作稍微的修改的。改动:
- 1,原mint-ui组件中style样式不能用了。
- 2,
const ToastConstructor = Vue.extend(require('./toast.vue'));
这种引入方式要换掉。
// ./acc-transfer-container.vue 本地应用,局部引入
<template >
<div class="acc-transfer-container" ref="template">
... ...
/** 点击“下一步”按钮,调用方法sayHello */
<el-button type="danger" round size="medium" @click="sayHello">下一步</el-button>
</div>
</template>
<script>
import Toast from '@/views/toast' /** 局部引入,在页面import引入,然后...向下看 */
export default {
.. ...
methods: {
... ...
sayHello(){
/** 通过点击”下一步“按钮,在这里当作方法一样直接使用,灵活度极高 */
Toast({
message: '坏人 , 说句hello听听~',
position: 'middle',
duration: 3000
});
},
... ...
},
... ...
};
</script>
<style lang='scss'>
body {
margin: 0 0;
padding: 0 0;
background-color: #f3f3f3;
}
... ...
</style>
本地vue工程
中程序实现
自定义一个消息弹窗组件,仿照mint-ui组件库中的源码进行编码。这里讲述全局配置
对于./message.vue文件
// ./message.vue
<template>
<div class="wrap">
<div class="message" :class="item.type" v-for="item in notices" :key="item._name">
<div class="content">{
{
item.content}}</div>
</div>
</div>
</template>
<script>
// 默认选项
const defaultOptions = {
duration: 1000,
type: 'info',
content: '一条提示信息!',
}
let mid = 0
export default {
name:'MyMessage', // 建议添加方便外面直接取值
data() {
return {
notices: [] // 用于存放message弹窗内容的某条信息
}
},
methods: {
/** 通过message.js中的vue实例,调用该方法。 */
addMessage(notice = {
}) {
// name标识 用于移除弹窗
let _name = this.getName()
// 合并选项
notice = Object.assign({
_name
}, defaultOptions, notice)
//装入数据,某条信息
this.notices.push(notice)
//延时移除某条信息
setTimeout(() => {
this.removeNotice(_name)
}, notice.duration)
},
getName() {
return 'msg_' + (mid++) //创建一个唯一的值
},
/** 移除缓存到数组中的内容 */
removeNotice(_name) {
let index = this.notices.findIndex(item => item._name === _name)
this.notices.splice(index, 1) // 删除当前超时的dom
}
}
}
</script>
<style scoped>
.wrap {
position: fixed;
top: 50px;
left: 50%;
display: flex;
flex-direction: column;
align-items: center;
transform: translateX(-50%);
}
... ...
</style>
// ./message.js
import Vue from 'vue'
import Message from './message.vue' // 引入vue模块
let messageInstance = null // 定义消息弹窗的实例
let init = (options) => {
let MessageConstructor = Vue.extend(Message) // 构造Message的vue子类
/** 创建子类实例 */
messageInstance = new MessageConstructor({
}) // 构造函数可以接传值,data、methods.....
console.log('mint-ui messageInstance>>>>', messageInstance)
// $mount()不带参数,会把组件在内存中渲染完毕
messageInstance.$mount() // 如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。返回值:vm - 实例自身
// messageInstance.$el 拿到的就是组件对应的dom元素,可以直接操作dom
document.body.appendChild(messageInstance.$el) /** 这里就被植入到了body中 */
messageInstance.$el.style.zIndex = 9999 // 设置该消息弹窗的样式展示层次
}
let caller = (options) => {
if (!messageInstance) {
// 进页面初始化
init(options)
}
// addMessage 是组件内部声明的方法,也可以通过构造函数传对应的方法
// 通过该方法实现对message.vue中绑定变量的动态赋值
messageInstance.addMessage(options)
}
// export default caller; /** 第一种对外导出的方式,供局部引入配置使用 */
/** 第二种对外导出的方式,通过此方式install(vue),可以在main.js中进行全局配置使用 */
export default {
install(vue) {
vue.prototype.$mymessage = caller
}
}
在main.js中进行全局配置使用,看这里:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Message from '@/views/msg' //全局引入,第一步
/** 安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。 */
Vue.use(Message) //全局引入,第二步
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app') /** $mount 是将vue替换了#app标签 */
应用到页面中,看这里:
//Bank-Acc-Transfer-c.vue
<template >
<div class="acc-transfer-container" ref="template">
... ...
/** 点击按钮 "下一步" 调用方法sayHello() 弹出消息弹窗*/
<el-button type="danger" round size="medium" @click="sayHello()">下一步</el-button>
</div>
</template>
<script>
export default {
... ...
methods: {
... ...
sayHello( ){
/** 弹窗的展示,通过直接调用Message(options)方法 */
/** 注意:这里是全局的,没有通过import引入哦 。*/
/** 如果以局部引入,通过import引入即可 */
Message({
content: '玩呢,这样也可以实现 !'});
}
},
... ...
};
</script>
<style lang='scss'>
body {
margin: 0 0;
padding: 0 0;
background-color: #f3f3f3;
}
... ...
</style>
总结extend
的主要应用方向
从使用性上来看,用到它的地方主要以灵活性
为主。它的灵活性,能起到高度解耦的作用。
为什么这么说,使用extend创建的vue子类后,可以动态植入到html的body中。而且可以做到随时植入,随时移除,高度解耦,快捷方便。