写点什么

【Vue2.x 源码学习】第三十八篇 - 组件部分 - 组件的编译

用户头像
Brave
关注
发布于: 2 小时前
【Vue2.x 源码学习】第三十八篇 - 组件部分 - 组件的编译

一,前言


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


  • 组件初始化情况;

  • 组件合并的位置;

  • 组件合并的策略;

  • 组件合并后测试;


本篇,组件部分-组件的编译;


二,组件的编译

1,组件的介绍


组件源于 WebComponent,即 Web 组件;原生支持自定义标签,但是兼容性不好;

所以,Vue 和 React 实现了一套组件 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>    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>
复制代码


局部组件,只能在声明作用域下被使用;

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>
复制代码


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

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

三,组件的初始化流程介绍

1,Vue 的全局 API

Vue.component 是全局 API,在 Vue 初始化 init 时,会对全局 API 做集中处理:


// src/index.js
/** * 在vue 中所有的功能都通过原型扩展(原型模式)的方式来添加 * @param {*} options vue 实例化传入的配置对象 */function Vue(options) { this._init(options); // 调用Vue原型上的方法_init}
initMixin(Vue)renderMixin(Vue)lifeCycleMixin(Vue)initGlobalAPI(Vue) // 初始化 global Api
export default Vue;
复制代码


initGlobalAPI 方法,处理全局 API

// src/global-api/index.js
export function initGlobalAPI(Vue) { // 全局属性:Vue.options // 功能:存放 mixin, component, filte, directive 属性 Vue.options = {}; Vue.mixin = function (options) { this.options = mergeOptions(this.options, options); return this; // 返回this,提供链式调用 } /** * Vue.component API * @param {*} id 组件名 * @param {*} definition 组件定义 */ Vue.component = function (id, definition) { }}
复制代码

2,Vue.component API

// 方法定义Vue.component = function (id, definition) {}  // 使用方式Vue.component('my-button',{  name:'my-button',  template:'<button>全局组件</button>'})
复制代码

2.1 组件名 name

每个组件都有一个自己的名字,即组件的唯一标识;


  • 默认组件名是:id,即 Vue.component 的第一个参数;

  • 若 definition 中有 name,使用 name 值作为组件名;


/**  * Vue.component  * @param {*} id          组件名  * @param {*} definition  组件定义  */Vue.component = function (id, definition) {  definition.name = definition.name || id;}
复制代码

2.2 组件定义 definition

Vue.component 的第二个参数 definition,即组件定义;definition 组件定义可以是函数,也可以是对象;


若 definition 为对象,Vue.component 方法内部会使用 Vue.extends 进行处理


// src/global-api/index.js
/** * 使用基础的 Vue 构造器,创造一个子类 * @param {*} definition */Vue.extends = function (definition) {
}
/** * Vue.component * @param {*} id 组件名 * @param {*} definition 组件定义:对象或函数 */Vue.component = function (id, definition) { // 获取组件名 name:优先使用definition.name,默认使用 id let name = definition.name || id; definition.name = name;
// 如果传入的definition是对象,需要用Vue.extends包裹 if(isObject(definition)){ definition = Vue.extends(definition) }}
复制代码

3,Vue.extend

定义:使用基于 Vue 构造器,创建一个子类;

// TODO 补充 Vue 官网相关内容

4,保存全局组件构造函数

initGlobalAPI 方法中,Vue.options 用于存放全局属性;

而在全局组件中,也要用到全局属性,所以,全局组件也要注册到 Vue.options 中;

Vue.options.components 用于存放全局组件:


// src/global-api/index.js
export function initGlobalAPI(Vue) { // 全局属性:Vue.options // 功能:存放 mixin, component, filte, directive 属性 Vue.options = {}; // 每个组件初始化时,将这些属性放入组件 // 用于存放全局组件 Vue.options.components = {}; /** * 使用基础的 Vue 构造器,创造一个子类 * @param {*} definition */ Vue.extends = function (definition) { } /** * Vue.component * @param {*} id 组件名(默认) * @param {*} definition 组件定义:可能是对象或函数 */ Vue.component = function (id, definition) {
// 获取组件名 name:优先使用definition.name,默认使用 id let name = definition.name || id; definition.name = name;
// 如果传入的definition是对象,需要用Vue.extends包裹 if(isObject(definition)){ definition = Vue.extends(definition) }
// 将 definition 对象保存到全局:Vue.options.components Vue.options.components[name] = definition;
}}
复制代码


Vue.options.components 相当于在全局维护了一个组件名与组件构造函数的映射关系;

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


创造一个组件,就相当于 new 组件类,此时,就会进行组件的初始化;


当 new Vue 时,会执行 this._init 方法:


// src/index.js
function Vue(options) { this._init(options); // 调用 Vue 的原型方法 _init}
initMixin(Vue)renderMixin(Vue)lifeCycleMixin(Vue)initGlobalAPI(Vue)
复制代码


_init 方法:


// src/init.js#initMixin
Vue.prototype._init = function (options) { const vm = this; vm.$options = mergeOptions(vm.constructor.options, options); initState(vm); // 状态的初始化 if (vm.$options.el) { // 将数据挂在到页面上(此时,数据已经被劫持) vm.$mount(vm.$options.el) }}
复制代码


所以,当 new 组件时,也应该会调用初始化方法:


function Vue(options) {    this._init(options);}
复制代码


这样,vm.constructor.options 中也就包含了 Vue.options.components

当 new vue 时,用户对局部组件进行了声明:

  <script>    new Vue({      el: "#app",      components:{        'my-button':{// 局部组件          template:'<button>Hello Vue 局部组件</button>'        }      }    });  </script>
复制代码


内部就会将“全局组件”和“局部组件”进行一次合并:

vm.$options = mergeOptions(vm.constructor.options, options);
复制代码


此时,vm.constructor.options(子)是一个函数,但 options(父)是一个对象:

  <script>    // 全局组件    Vue.component('my-button',{ // 内部会被 Vue.extends 处理,成为一个构造函数      name:'my-button',      template:'<button>全局组件</button>'    })    new Vue({      el: "#app",      // 局部组件      components:{  // 不会被 Vue.extends 处理,就真的是一个对象        'my-button':{          template:'<button>局部组件</button>'        }      }    });  </script>
复制代码


这样,就维护好了组件间的层级关系;

组件的查找规则:优先找自己,找不到通过链上去找父亲


6,组件的合并策略


html 模板会被解析称为 AST 语法树:

  <div id="app">    <my-button></my-button>  </div>
复制代码


模板编译流程:

  • 将模板解析称为 AST 语法树;

  • 根据 AST 语法树生成 render 函数;


当前在 render 函数中,如果是标签会调用 _c 方法处理;

c 方法之前只处理元素,但现在有可能是组件了!  即: c('组件名')

即 createElm 中的 tag 可能是组件;


此处需要进行扩展:

  • 如果是组件,就创建组件 CreateComponent

  • 如果不是组件就创建元素


在 createComponent 方法中,创造组件的虚拟节点 componentVnode,

此时组件的构造函数this.$options.components[tag],不是函数就是对象;

  • 如果是对象,调用 Vue.extend 处理

7,组件的渲染和更新


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

组件初始化时,会为每个组件创建一个 watcher,属性会收集对应组件渲染 watcher 添加到自身 dep 记录;

当组件更新时,根据 dep 收集结果,更新组件对应的 watcher;


四,结尾

本篇,介绍了组件部分-组件的编译,主要涉及以下几部分:


下一篇,。。。

用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【Vue2.x 源码学习】第三十八篇 - 组件部分 - 组件的编译