介绍
vue.extend是一个全局的api,生成的是vue的子类是实现编程式组件的一个重要途径。在阅读源码时我们要带着几个问题:
- vue.extend如何使用?
- vue.extend常用的使用场景有哪些?
- vue.extend内部原理是怎样实现的?
- 通过vue.extend实现一个编程式组件
基本用法
探究一个API或者一个方法的实现原理时首先我们必须要知道这个API如何使用,才能更好的探究他的实现原理。
参数
{Object} options
用法
使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数。
<div id="mount-point"></div>
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
结果
<p>Walter White aka Heisenberg</p>
使用场景
在element-ui(以下名称用el代替)中我们可以通过如下方式使用组件
//message组件
this.$message.success("提交成功");
//弹窗
this.$alert('这是一段内容', '标题名称', {
confirmButtonText: '确定',
callback: action => {
this.$message({
type: 'info',
message: `action: ${ action }`
});
}
});
//通知组件
this.$notify({
title: '成功',
message: '这是一条成功的提示消息',
type: 'success'
});
el组件是怎么做到可以通过vue的实例就可以调用组件的呢?用message组件为例:
//引入组件
import Message from '../packages/message/index.js';
//把组件挂载到vue的原型对象上
Vue.prototype.$message = Message;
Message内部又是怎么实现可以通过JS调用去渲染组件的呢?我会去掉暂时不用的代码,让我们更方便的阅读
//引入普通组件
import Main from './main.vue';
//创建vue的子类
let MessageConstructor = Vue.extend(Main);
...
//实例化组件
instance = new MessageConstructor({
data: options
});
//渲染组件
instance.$mount();
//挂载组件
document.body.appendChild(instance.$el);
...
return instance;
}
...
通过代码可知 vue.extend和mount()方法是实现JS调用渲染组件的精髓所在。所以当你也想在vue中使用JS调用去渲染组件就使用它吧
内部实现
当我们知道了他的使用方式和使用场景,那他是怎么实现的呢?传入一个配置为什么可以生成vue的子类?我先把全部的代码贴上去,我会通过备注的方式分析。
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {};//传入了参数使用传入的如果没穿使用{}
var Super = this; //这里指向vue
var SuperId = Super.cid;//用作缓存
//如果传入的对象参数不存在_Ctor属性,extend会给他添加上这个属性,用于缓存。
//如果存在这个属性,并且这个对象上存在SuperId这个键的话就会直接返回缓存过的构造函数
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
//校验name名称是否符合规则
var name = extendOptions.name || Super.options.name;
if (name) {
validateComponentName(name);
}
//这就是子类的构造函数
var Sub = function VueComponent (options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype); //继承vue的原型方法
Sub.prototype.constructor = Sub; //把constructor的指向自己
Sub.cid = cid++;//Sub添加一个自己的属性cid用于缓存
Sub.options = mergeOptions( //合并配置
Super.options,
extendOptions
);
Sub['super'] = Super; //把父类(也就是vue)挂载到super属性方便以后使用
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
//判断是否有props属性如果有就初始化props
if (Sub.options.props) {
initProps$1(Sub);
}
//判断是否有computed属性如果有就初始化computed
if (Sub.options.computed) {
initComputed$1(Sub);
}
// allow further extension/mixin/plugin usage
//把父类的extend mixin use 方法挂载到Sub上允许他进一步扩展
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// create asset registers, so extended classes
// can have their private assets too.
//ASSET_TYPES是数组['component','directive','filter']
//把super(父类)的component,directive,filter 方法挂载到sub(子类)上
//使sub具有注册组件 ,注册指令,注册自定义过滤器
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type];
});
// enable recursive self-lookup
//name有值的时候将自己存到options.components[name]上
if (name) {
Sub.options.components[name] = Sub;
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
//把父类的配置,传入的配置和合并的配置挂载到sub(子类)上
//方便以后检查options是否有更新。
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);
// cache constructor
//把自己缓存到Sub.options._Ctor对象上并返回Sub
cachedCtors[SuperId] = Sub;
return Sub
};
实现编程式组件
那自己应该怎么通过vue.extend实现一个编程式组件呢?下面我们自己实现
this.$AFMessage.success("成功")
this.$AFMessage({
type:"success",
message:"成功"
})
这两种方式使用组件。
创建组件(用于编程式调用)
<template>
<div :class="messageClass" v-if="!closed">
<i :class="typeClass"></i>
<p class="af-message__content">
{{ message }}
</p>
</div>
</template>
<script>
const typeMap = {
success: 'success',
info: 'info',
warning: 'warning',
error: 'error'
};
export default {
name: 'af-message',
data() {
return {
message: '测试信息', //显示信息
type: 'success', //显示类型
closed: false, //是否显示
duration: 3000, //自动消失时间为0时不消失
timer: ''
};
},
computed: {
typeClass() {
return this.type ? `af-message__icon af-icon-${typeMap[this.type]}` : '';
},
messageClass() {
return this.type ? `af-message af-message--${this.type}` : `af-message`;
}
},
created() {
this.startTimer();
},
methods: {
close() {
this.closed = true;
},
startTimer() {
if (this.duration > 0) {
this.timer = setTimeout(() => {
if (!this.closed) {
this.close();
clearTimeout(this.timer);
}
}, this.duration);
}
}
}
};
</script>
<style lang="scss">
.af-message {
border-radius: 4px;
overflow: hidden;
min-width: 380px;
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: #ebeef5;
position: fixed;
left: 50%;
top: 20px;
transform: translateX(-50%);
background-color: #edf2fc;
transition: opacity 0.3s, transform 0.4s, top 0.4s;
padding: 15px 15px 15px 20px;
display: flex;
align-items: center;
.af-message__content {
padding: 0;
font-size: 14px;
line-height: 1;
margin: 0;
}
}
.af-message--success {
background-color: #f0f9eb;
border-color: #e1f3d8;
.af-message__content {
color: #67c23a;
}
}
</style>
这块没什么要说的,主要实现组件的显示。组件的样式我是模仿element组件写的。下面是重点
实现编程式
import vue from 'vue';
import afMessage from './message.vue';
//创建构造器
const AFMessage = vue.extend(afMessage);
function messgae(option) {
option = option || {};
if (option === 'string') {
option = { messgae: option };
}
//生成你导入的组件
const instance = new AFMessage({
data: option
});
//挂载组件
instance.$mount();
document.body.appendChild(instance.$el);
return instance;
}
//支持this.$AFMessage.success()调用
['success', 'error', 'info'].map((itemType) => {
messgae[itemType] = function(options) {
if (typeof options === 'string') {
options = {
message: options
};
}
options.type = itemType;
return messgae(options);
};
});
export default messgae;
注册
main.js
import Vue from 'vue';
import AFmessage from '@/common/components/afMessge';
Vue.prototype.$AFMessage = AFmessage;
使用
this.$AFMessage({
type:"success",
message:"成功"
})
这样就可以在项目中愉快的使用了。
总结
vue.extend说到底就是创建vue的子类。