一,前言
上篇,介绍了组件部分-组件的合并,主要涉及以下几个点:
组件初始化情况;
组件合并的位置;
组件合并的策略;
组件合并后测试;
本篇,组件部分-组件的编译;
二,组件的编译
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
每个组件都有一个自己的名字,即组件的唯一标识;
/** * 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 方法中,创造组件的虚拟节点 componentVnode,
此时组件的构造函数this.$options.components[tag],不是函数就是对象;
7,组件的渲染和更新
根据组件的虚拟节点,创建出组件的真实节点;并将组件插入到父元素中;
组件初始化时,会为每个组件创建一个 watcher,属性会收集对应组件渲染 watcher 添加到自身 dep 记录;
当组件更新时,根据 dep 收集结果,更新组件对应的 watcher;
四,结尾
本篇,介绍了组件部分-组件的编译,主要涉及以下几部分:
下一篇,。。。
评论