简介
Vue.extend作为一个全局api,当然值得我们去深入学习的,同时也是实现编程式组件的重要途径,所以我们通过源码导读加实践的方式开始吧。首先我们会带着几个问题来进行学习,如果你都不会,哈哈哈恭喜你,学完本篇你就会明白了。
- Vue.extend在Vue当中起到的作用?
- 讲讲Vue.extend内部实现?
- 实现一个编程式组件,说说具体思路?
目录
- 基本用法
- 源码赏析
- 源码导读
- 手动实现一个编程式组件
- 总结
基本用法
参数:
{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>
源码赏析
在我们赏析源码之前,我们首先肯定要找到这个api及调用的位置,这样才能更好的理解它的作用。通过官方用法我们就知道,它肯定和组件有关系,那么我们很容易找到在源码中创建组件的位置,接下来我们一步一步找:
1创建组件:在src/core/vdom/create-element:(113)
vnode = createComponent(Ctor, data, context, children, tag)
2进入createComponent函数:在src/core/vdom/create-component:(115)
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
这里, baseCtor其实就是Vue,具体原因在以后分析组件源码会讲解,这里不作研究。我们终于找到了Vue.extend调用的位置,就是在Vue创建每个组件的时候调用,通过构造器创建子类。下面就是完整的源码:
Vue.cid = 0
let cid = 1
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = 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.
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
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.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
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.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}
}
接下来我们就一步一步彻底搞懂源码。
源码导读
a:
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
首先,extendOptions使我们传入进去的模板,这里面的this就是调用extend的对象,就是Vue,然后保存在变量Super中,变量SuperId就保存Vue中的唯一标识(每个实例都有自己唯一的cid).
b:
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
这一段作为缓存策略的,放在下面说。
c:
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
用一个name变量来保存组件的名字,就是我写组件时候的name,如果没写就使用父组件的name,然后对name通过validateComponentName函数验证,主要就是判断name不能是html元素和不能是非法命名。
d:
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
上面我们创建一个子类Sub,这里我们通过继承,使Sub拥有了Vue的能力,并且添加了唯一id(每个组件的唯一标识符)
e:
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
这里调用了mergeOptions函数实现了父类选项与子类选项的合并,并且子类的super属性指向了父类。
f:
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
初始化了props和computed.
g:
// allow further extension/mixin/plugin usage
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.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
将父类的方法复制到子类,包括extend,mixin,use,component,directive,filter.还新增属性superOptions,extendOptions,sealedOptions 。
h:
// cache constructor
cachedCtors[SuperId] = Sub
之前的代码结合,将父类的id保存在子类的属性上,属性值为子类,在之前会进行判断如果构造过子类,就直接将父类保存过的id值给返回了,就是子类本身不需要重新初始化,,作为一个缓存策略。
总体来说,其实就是创建一个Sub函数并继承了父级。
手动实现一个编程式组件
我们在一般调用组件时,都会先组件注册,再在模板中引用。一个两个还是可以接受的,那么如果这个组件很多很多呢,所以每次注册应用就显得力不从心了。用过element-ui我们都知道,可以不用注册,直接通过命令调用:
this.$message.success('成功了')
是不是特别特别方便,简单明了,而且还符合编程思维。接下来我们将手动实现一下:
a:创建一个组件(用于编程式调用的)
<template>
<div class='toast'
v-show='isShow'>
{{message}}
</div>
</template>
<script>
export default {
data () {
return {
message: '',
isShow: false
}
},
methods: {
show (message, duration) {
this.message = message
this.isShow = true
setTimeout(() => {
this.isShow = false
this.message = ''
}, duration)
}
}
}
</script>
<style scoped>
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999;
padding: 8px 10px;
background-color: rgba(0, 0, 0, 0.3);
}
</style>
这个组件非常简单,两个变量分别是显示的消息和是否显示。我还定义了一个方法,作用就是使模板取消隐藏,并且传入一个duration参数通过定时器表示显示的时间,时间一到isShow就变false,可以说是非常简单了,模板有了,下面开始重点了。
b:实现编程式
import Toast from './toast'
const obj = {}
obj.install = function (Vue) {
// 创建构造器
const ToastContrystor = Vue.extend(Toast)
// new的方式 根据组件构造器,可以创建组件对象
const toast = new ToastContrystor()
// 手动挂载某一个元素上
toast.$mount(document.createElement('div'))
// toast.$el对应的就是div
document.body.appendChild(toast.$el)
//组件挂载到Vue原型上
Vue.prototype.$toast = toast
}
export default obj
把组件当作插件一样,通过Vue.use()使用,注释都写的非常清楚了,一步一步理解。
c:在main.js注册
import Vue from 'vue'
import toast from '@/components/common/toast/index.js'
Vue.use(toast)
d:使用
this.$toast.error('注册失败,请重新输入', 1000)
就可以在项目各个地方调用了。我们就实现了一个编程式的组件。
总结
VUe.extend总体来说其实就是创建一个类来继承了父级,顶级一定是Vue.这个类就表示一个组件,我们可以通过new的方式来创建。学习了extend我们就很容易的实现一个编程式组件。