【手写 Vue2.x 源码】第三十七篇 - 组件部分 - 组件的合并
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
一前言
上篇介绍了 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
- 添加了少量的三级标题使内容分布更加清晰
- 微调了部分描述与代码注释有助于更好的理解
- 部分内容进行规整形成无需列表使思路的表述更加清晰易懂
- 添加了部分图示与实际测试结果