【手写 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.optionsoptions进行合并

  • vm.constructor.options包含Vue.options.components中的全局组件
  • options 为执行 new Vue 时传入的局部组件选项;

即在这个位置就会进行全局组件局部组件的合并

函数 or 对象分析

  • vm.constructor.options中的全局组件 my-button 是一个函数
  • 而在 options 中的局部组件 my-button 是一个对象

image.png

image.png

原因分析:

  • 全局组件定义 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.optionsoptions进行合并先找局部组件再找全局组件

// 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 方法中会找到预设的组件合并策略函数

image.png

组件合并

此时参数 parentVal 是一个函数参数 childVal 是一个对象

image.png

生成的新对象 res 可以在链上拿到 parentVal 上的全局组件

image.png

再将儿子全部合并到新生成的 res 对象上

image.png

这样在 res 上查找组件时会先查找局部组件如果没找到则继续通过链找到全局组件

优先查找局部组件如果没有会沿着链向上继续找找到局部组件


三结尾

本篇介绍了组件部分-组件的合并主要涉及以下几个点

  • 组件初始化情况
  • 组件合并的位置
  • 组件合并的策略
  • 组件合并后测试

下一篇组件部分-组件的编译


维护日志

  • 20210812
    • 添加了少量的三级标题使内容分布更加清晰
    • 微调了部分描述与代码注释有助于更好的理解
    • 部分内容进行规整形成无需列表使思路的表述更加清晰易懂
    • 添加了部分图示与实际测试结果
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: vue

“【手写 Vue2.x 源码】第三十七篇 - 组件部分 - 组件的合并” 的相关文章