一,前言
上篇,介绍了 Vue.extend 实现,主要涉及以下几个点:
- Vue.extend 简介;
- Vue.extend 实现,包括:组件初始化;子类继承父类;修复 constructor 指向问题;
本篇,组件部分 - 组件的合并策略;
二,组件合并策略
1,前文回顾
在前面的两篇中,分别介绍了组件部分的 Vue.component 和 Vue.extend 实现:
- Vue.component:定义组件并维护到全局
Vue.options.components
中; - Vue.extend:根据选项参数创建子类,即组件的构造函数;
2,组件合并的位置
执行情况分析
<script>
Vue.component('my-button',{
name:'my-button',
template:'<button>全局组件</button>'
})
</script>
上边的Vue.component
执行完成后,Vue.options.components
中就已经存储了全局组件的构造函数;
<script>
Vue.component('my-button',{
name:'my-button',
template:'<button>全局组件</button>'
})
new Vue({
el: "#app",
components:{
'my-button':{
template:'<button>局部组件</button>'
}
}
});
</script>
当 new Vue 执行时,会进行组件的初始化流程,调用 _init 方法:
// src/init.js#initMixin
Vue.prototype._init = function (options) {
const vm = this; // this 指向当前 vue 实例
// vm.$options = options; // 将 Vue 实例化时用户传入的options暴露到vm实例上
// 此时需使用 options 与 mixin 合并后的全局 options 再进行一次合并
vm.$options = mergeOptions(vm.constructor.options, options);
// 目前在 vue 实例化时,传入的 options 只有 el 和 data 两个参数
initState(vm); // 状态的初始化
if (vm.$options.el) {
// 将数据挂在到页面上(此时,数据已经被劫持)
vm.$mount(vm.$options.el)
}
}
其中,mergeOptions
方法对vm.constructor.options
和options
进行合并:
vm.constructor.options
包含Vue.options.components
中的全局组件;options
为执行 new Vue 时传入的局部组件选项;
即在这个位置,就会进行全局组件和局部组件的合并;
函数 or 对象分析
- 在
vm.constructor.options
中的全局组件my-button
是一个函数; - 而在
options
中的局部组件my-button
是一个对象;
原因分析:
- 全局组件定义 Vue.component 中,传入的对象,内部会被 Vue.extends 处理成为函数;
- 内部组件定义 components 中,传入的对象,内部不会被 Vue.extends 处理,仍是对象;
<script>
// 全局组件
Vue.component('my-button',{ // 内部被 Vue.extends 处理,成为一个构造函数
name:'my-button',
template:'<button>Hello Vue 全局组件</button>'
})
new Vue({
el: "#app",
components:{ // 这里不会被 Vue.extends 处理,就真的是一个对象
'my-button':{// 局部组件
template:'<button>Hello Vue 局部组件</button>'
}
}
});
</script>
3,组件合并的策略
策略模式
在之前做 mixin 生命周期的合并时,在 mergeOptions 方法中使用了策略模式:
- 针对不同生命周期钩子,声明各自的合并策略;
- 如果在没有找到对应的策略,默认使用新值覆盖老值;
let strats = {}; // 存放所有策略
let lifeCycle = ['beforeCreate','created','beforeMount','mounted'];
// 创建各生命周期的合并策略
lifeCycle.forEach(hook => {
strats[hook] = function (parentVal, childVal) {
// 在strats策略对象中,定义了各生命周期的合并策略
}
})
/**
* 对象合并:将childVal合并到parentVal中
* @param {*} parentVal 父值-老值
* @param {*} childVal 子值-新值
*/
export function mergeOptions(parentVal, childVal) {
let options = {};
for(let key in parentVal){
mergeFiled(key);
}
for(let key in childVal){
// 当新值存在,老值不存在时:添加到老值中
if(!parentVal.hasOwnProperty(key)){
mergeFiled(key);
}
}
// 合并当前 key
function mergeFiled(key) {
// 策略模式:获取当前 key 的合并策略
let strat = strats[key];
if(strat){
options[key] = strat(parentVal[key], childVal[key]);
}else{ // 默认合并策略:新值覆盖老值
options[key] = childVal[key] || parentVal[key];
}
}
return options;
}
这样,就可以通过策略模式,实现了在 strats 中配置 component 对应的合并策略;
在 mergeOptions 方法执行并处理 component 合并时,就会根据配置好的策略进行合并;
合并策略
将vm.constructor.options
和options
进行合并,先找局部组件再找全局组件;
// parentVal为函数;childVal为对象;
strats.component = function (parentVal, childVal) {
// 继承:子类可以沿着链找到父类的属性 childVal.__proto__ = parentVal
let res = Object.create(parentVal);
if(childVal){
for (let key in childVal) {
res[key] = childVal[key];
}
return res;
}
}
4,组件合并后测试
测试组件合并
<script>
// 全局组件
Vue.component('my-button',{
name:'my-button',
template:'<button>Hello Vue 全局组件</button>'
})
new Vue({
el: "#app",
components:{// 局部组件
'my-button':{
template:'<button>Hello Vue 局部组件</button>'
}
}
});
</script>
在 mergeOptions 方法中,会找到预设的组件合并策略函数:
组件合并:
此时,参数 parentVal 是一个函数;参数 childVal 是一个对象;
生成的新对象 res 可以在链上拿到 parentVal 上的全局组件:
再将儿子全部合并到新生成的 res 对象上:
这样,在 res 上查找组件时,会先查找局部组件,如果没找到,则继续通过链找到全局组件;
优先查找局部组件,如果没有会沿着链向上继续找,找到局部组件;
三,结尾
本篇,介绍了组件部分-组件的合并,主要涉及以下几个点:
- 组件初始化情况;
- 组件合并的位置;
- 组件合并的策略;
- 组件合并后测试;
下一篇,组件部分-组件的编译;
维护日志
- 20210812:
- 添加了少量的三级标题,使内容分布更加清晰;
- 微调了部分描述与代码注释,有助于更好的理解;
- 部分内容进行规整,形成无需列表,使思路的表述更加清晰易懂;
- 添加了部分图示与实际测试结果;