【手写 Vue2.x 源码】第三十四篇 - 组件部分 - Vue 组件与初始化流程简介

375 阅读4分钟

一,前言

上篇,进行了 diff 算法阶段性梳理,主要涉及以下几个点:

  • 初渲染与视图更新流程;
  • diff 算法的外层更新;
  • diff 算法的比对优化;
  • diff 算法的乱序比对;
  • 初渲染和更新渲染判断;

本篇,组件的初始化流程介绍;


二,组件使用介绍

1,组件的介绍

组件,即自定义标签,设计理念源于 WebComponent,也就是 Web 组件;原生支持自定义标签,但是兼容性并不好;

所以,VueReact就各自实现了一套自己的组件 API

2,组件的定义

vue中,组件分为全局组件自定义组件两种,定义方式如下:

2.1 全局组件

<body>

  <div id="app1">
    <!-- 使用 my-button 组件 -->
    <my-button></my-button>
  </div>
  
  <div id="app2">
    <!-- 使用 my-button 组件 -->
    <my-button></my-button>
  </div>
  
  <script>
    // 定义 my-button 组件
    Vue.component('my-button',{
      template:'<button>Hello Vue</button>'
    })
    
    new Vue({
      el: "#app"
    });
  </script>
</body>

全局组件通过 Vue.component('xxx',{...})进行定义,能够在全局范围内使用;

2.2 局部组件

<body>

  <div id="app1">
    <!-- 可以使用 -->
    <my-button></my-button>
  </div>
  
  <div id="app2">
    <!-- 不可以使用 -->
    <my-button></my-button>
  </div>
  
  <script>
    new Vue({
      el: "#app1",
      // 声明局部组件:只能在组件声明的作用域 app1 下使用
      components:{
        'my-button':{
          template:'<button>Hello Vue 局部组件</button>'
        }
      }
    });
  </script>
</body>

局部组件,在Vue初始化传入的Options中声明,只能在声明作用域下被使用;

3,组件的优先级

当全局声明的组件与局部声明的组件名称相同时,哪个组件生效?优先级规则怎么样?

<body>

  <div id="app">
    <my-button></my-button>
  </div>
  
  <script>
    // 全局组件
    Vue.component('my-button',{
      template:'<button>Hello Vue 全局组件</button>'
    })
    
    // 局部组件
    new Vue({
      el: "#app",
      components:{
        'my-button':{
          template:'<button>Hello Vue 局部组件</button>'
        }
      }
    });
  </script>
</body>

同名的全局组件和局部组件同时存在时,根据组件查找规则,优先使用局部组件;

相同名称的全局组件和局部组件定义并不会被覆盖,而是会像原型链一样,逐级向上进行查找;

组件作用域的实现应用了原型的概念:优先查找实例上的component,如果没有找到,再通过链继续向上进行查找;


三,组件初始化流程简介

1,Vue.component

Vue.component 是一个全局 API;

Vue 初始化流程的 initGlobalAPI 方法中,对 Vue Global API 进行集中处理;

// Vue.component 方法定义
Vue.component = function (id, definition) { ... }

// Vue.component 的使用方式
Vue.component('my-button',{
  name:'my-button',
  template:'<button>全局组件</button>'
})

在早期的 Vue2 中,定义组件并不是使用 Vue.component,而是 使用 Vue.extend 来进行组件的声明;

那么,Vue.componentVue.extend 有什么关系呢?

2,Vue.extend

根据官方的 API 文档,对比两个这 API:

image.png

Vue.extend 作用:使用基础 Vue 构造器,创造一个子类(即组件的构造函数);可以通过 new 这个对象创建组件实例;

image.png

Vue.component 中,有两个参数:

  • 第一个参数 id :组件的唯一标识;优先使用 name 属性;
  • 第二个参数 definition :组件定义,可以是 Function(异步组件)或 Object(普通定义);
    • 1)definition 可以直接传入一个 Vue.extend
    • 2)definition 可以传入一个对象;此时,在 Vue.component内部会使用Vue.extend进行一次处理;

从官网文档的介绍中可以看出,在 Vue.component 的实现中,使用了 Vue.extend

3,保存组件构造函数

将组件名 name 与构造函数 definition 的映射关系,保存到全局对象 Vue.options.components 中;

在全局组件中,需要使用全局属性;

在组件的初始化时,全局属性会被放到每个组件上

所以,全局组件也需要被注册到 Vue.options 上;即保存到全局对象 Vue.options.components 中;同时便于后续组件合并时进行查找;

4,全局组件与局部组件合并

Vue 初始化时执行的 _init 方法中,会通过 mergeOptions 方法对 Options 选项进行合并;

mergeOptions 方法内部,会根据组件的合并策略,完成“全局组件”和“局部组件”的合并操作;

备注:此时,mergeOptions 方法的 vm.constructor.options 中已经包含了 Vue.options.components

组件的查找规则:优先找自己,如果找不到再继续通过链向上找父亲;

5,组件的合并策略

模板编译流程:html模板 -> AST语法树 -> render函数;

render 函数中,会通过 _c(即 createElm 方法)处理标签和组件;

createComponent 方法:创建组件的虚拟节点 componentVnode

6,组件的渲染和更新

根据组件的虚拟节点,创建出组件的真实节点,并将组件插入到对应的父元素中;

在组件初始化时,会为每个组件创建出一个 watcher 渲染函数;

在依赖收集阶段,属性会收集对应组件的渲染 watcher 并记录到其 dep 属性中;

当组件更新时,遍历通知 dep 数组中对应的 watcher 执行组件更新;


四,结尾

本篇,介绍了 Vue 组件与初始化流程,涉及以下几部分:

  • 组件使用介绍:全局组件、局部组件的定义和优先级;
  • 组件初始化流程介绍:Vue.component、Vue.extend、保存组件构造函数、组件合并、初渲染和更新;

下一篇,Vue.component 的实现;

todo:本篇,作为组件部分的整个流程介绍,后期应该着重从全局视角对整个流程进行说明,比如:描述关键步骤并画出流程图;


维护日志

  • 20210808:更新文章标题为“组件部分-Vue组件与初始化流程简介”;调整“组件初始化流程简介”部分内容:体现组件初始化流程中各主要环节及对应的文章更新计划;
  • 20210813:修改部分文字和单词拼写错误;
  • 20230223:添加了官方文档截图;添加了内容中的代码和关键字高亮;优化了部分内容描述,使表述更加清晰易懂;更新了文章摘要;
  • 20230225:调整了部分描述;添加了 todo;