前言
在此之前页面渲染的流程,以及更新渲染的流程已经讲完了。我们在使用Vue时用到最多的其实还是组件,那么我们创建的组件又是如何渲染在页面当中的呢?
我个人推荐的看源码的方式最好是通过视频文章自己能手写一边然后再去看,否则就会造成这样的结果。
真是卷...
组件注册
ini
// core/global-api/index.js
const ASSETS_TYPE = ["component", "directive", "filter"];
function initGlobalApi(Vue) {
Vue.options = {};
ASSETS_TYPE.forEach(type => {
Vue.options[type + "s"] = {};
});
}
initGlobalApi
方法主要就是在Vue
上挂载了静态对象options
,用于存储后续注册的component
, directive
, filter
。
ini
const ASSETS_TYPE = ["component", "directive", "filter"];
export default function initAssetRegisters(Vue) {
ASSETS_TYPE.forEach(type => {
Vue[type] = function(id, definition) {
if(id === 'component') {
definition = Vue.extend(definition);
}
this.options[type + "s"][id] = definition;
}
})
}
这里主要将组件实例通过全局Vue.extend
注册在component
上,后续寻找组件就options.components
上找。
Vue.extend
javascript
export function initExtend(Vue) {
Vue.extend = function (options = {}) {
function Sub(options = {}) { // 组件构造函数,并且调用init初始化方法
this._init(options);
}
Sub.prototype = Object.create(Vue.prototype); // 组件原型指向Vue原型
Sub.prototype.constructor = Sub; // 组件构造函数指向自己
Sub.options = mergeOptions(Vue.options, options);
return Sub; // 返回组件构造函数
}
}
extend
主要就是创建了子类的构造函数,并且进行初始化。通过mergeOptions
将当前组件传入的options
和我们全局的options
进行合并,并且返回子类。
创建组件Vnode
javascript
export const isReservedTag = (tag) => { // 是否是普通标签 这里只是举例了几个
return ['a', 'div', 'h1', 'h2', 'h3', 'span', 'p', 'button', 'body', 'html'].includes(tag);
}
export function createElement(vm, tag, data = {}, ...children) {
if (!data) {
data = {};
}
let key = data.key;
if (key) {
delete data.key;
}
if (isReservedTag(tag)) { // 判断是不是普通元素标签
return vnode(vm, tag, key, data, children);
} else {
let Ctor = vm.$options.components[tag];
return createComponentVnode(vm, tag, key, data, children, Ctor);
}
}
export function createComponentVnode(vm, tag, key, data, children, Ctor) { // 创建组件虚拟dom
if (typeof Ctor === 'object') { // 如果传入的是个对象,就需要extend来转为组件构造函数
Ctor = vm.constructor.extend(Ctor);
}
data.hook = { // 在属性上定义了组件钩子函数
init(vnode) { // 创建真实节点所调用, 组件初始化钩子
let instance = vnode.componentInstance = new vnode.componentOptions.Ctor();
instance.$mount();
}
}
// 返回组件虚拟Dom
return vnode(vm, tag, key, data, children, null, {
Ctor
});
}
在调用createElement
创建Vnode
时,它会判断当前标签名是否是普通标签,还是组件标签。如果时组件就需要从全局options.components
中获取对应的组件构造函数,也有可能时组件实例对象,所以这里需要判断下,如果不是组件构造函数,就需要调用extend
转为构造函数。并且在属性data
上定义了init
钩子函数,进行组件的挂载。
Vnode转为真实Dom
ini
function createElm(vnode) {
let { tag, data, children, text } = vnode;
if (typeof tag === 'string') {
// 创建真实元素需要区分是不是组件
if (createComponentVnode(vnode)) {
return vnode.componentInstance.$el;
}
vnode.el = document.createElement(tag);
patchProps(vnode.el, {}, data);
children.forEach(child => {
vnode.el.appendChild(createElm(child));
})
} else {
vnode.el = document.createTextNode(text);
}
return vnode.el;
}
function createComponentVnode(vnode) {
let i = vnode.data;
if ((i = i.hook) && (i = i.init)) {
i(vnode); // 初始化组件
}
if(vnode.componentInstance) { // 说明是组件
return true;
}
}
在将vnode
转为真实Dom
中,判断data上是否存在钩子函数,存在钩子函数的便是组件,直接调用钩子函数init
在vnode
上定义componentInstance
,直接返回vnode.componentInstance.el
进行挂载
总结
组件定义就是将我们写的options
对象通过extend
转为子类Vue
构造函数,并执行初始化方法,将组件定义在全局options.components
上,以方便我们后续取组件。在创建组件Vnode
时,判断当前标签是否时普通元素标签,如果是组件标签即会在data
属性上定义init
钩子函数,钩子即调用$mount
进行挂载渲染。将虚拟dom
转为真实dom
其实就是判断当前data
是否存在钩子函数,存在就是执行init
进行挂载即可。
如果觉得本文有帮助 记得点赞三连哦 十分感谢!