一,前言
上篇,介绍了组件部分-组件的合并,主要涉及以下几个点:
组件初始化情况;
组件合并的位置;
组件合并的策略;
组件合并后测试;
本篇,组件部分-组件的编译;
二,组件的编译
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;
四,结尾
本篇,介绍了组件部分-组件的编译,主要涉及以下几部分:
下一篇,。。。
评论