一,前言
上篇,组件部分-生成组件的真实节点;
本篇,组件部分-组件挂载流程分析;
二,组件挂载流程分析
1,示例
- 全局组件:my-button,name:'全局组件';
- 局部组件:my-button;name:'局部组件';
<body>
<div id="app">
<my-button></my-button>
</div>
<script src="./vue.js"></script>
<script>
// 全局组件
Vue.component('my-button',{
name:'my-button',
template:'<button>Hello Vue {{name}}</button>',
data(){
return { name: '全局组件'}
}
})
// 局部组件
new Vue({
el: "#app",
components:{
'my-button':{
template:'<button>Hello Vue {{name}}</button>',
data(){
return { name: '局部组件'}
}
}
}
});
</script>
2,组件的挂载流程
// src/init.js
export function initMixin(Vue) {
// 初始化
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)
}
}
Vue.prototype.$mount = function (el) {
const vm = this;
const opts = vm.$options;
el = document.querySelector(el);
vm.$el = el;
if (!opts.render) {
let template = opts.template;
if (!template) {
template = el.outerHTML;
}
let render = compileToFunction(template);
opts.render = render;
}
mountComponent(vm);
}
Vue.prototype.$nextTick = nextTick;
}
第一次进入,为根节点初始化,el 为 #app;
第二次进入,组件的初始化,el 为 undefined;
- 继续,进行组件的挂载操作
mountComponent: - 组件的挂载:核心是创造出一个组件的虚拟节点,并调用组件的更新方法
_update: render.call创造组件的虚拟节点vnode,即button的虚拟节点:vm.render执行完成后,继续执行_update方法:- 这时,不能获取到上一次的虚拟节点:
- 当组件挂载时,
$patch方法中的el为null; - 此时,
patch内部判断了,如果oldVnode为null,就使用组件的虚拟节点,创建出组件的真实节点: - 此时,返回的
vm.$el就是button内部包含着内容:
此时,子组件就挂载完毕了!
vnode.componentInstance存在,则说明是组件,createComponent方法返回 true:- 在
createElm方法中,返回组件的$el,即组件对应的真实节点button: createElm递归处理,将生成的组件真实节点,放到对应的父节点上;- 再将完整的
div挂载到页面上;
三,完整流程
1,实现了 Vue.component,它的核心功能是注册成全局组件;
内部会自动调用 Vue.extend 方法,返回组件的构造函数;
2,在组件初始化时,会进行组件的合并 mergeOptions;
mergeOptions:优先查找局部组件,若找不到,则继续通过链向上查找全局组件;
3,组件合并完成之后,内部会对模板进行编译操作,最终会走到 _c('组件名'):
进行标签筛查:
- 如果是组件,创建组件虚拟节点;在组件的虚拟节点中,最重要的就是
componentOptions,里面存放了组件的所有内容(属性的实现、事件的实现、插槽的内容、Ctor); - 而且,还会判断
Ctor,如果是对象,会调用Vue.extend(componentOptions)处理为组件的构造函数;
所以,所有的组件都是通过
Vue.extend方法来实现的;
4,创建组件的虚拟节点
create执行完成后,就该走patch了,在patch方法中,有一个非常重要的createElm方法:将虚拟节点转化为真实节点;- 在组件生命周期
hook:init中,通过let child = new Ctor({})得到组件实例child; - 在
new 子类时,会调用子类的初始化_init,同时,会把子类的选项和它的选项进行合并,并且对组件中的data数据进行初始化,劫持数据做响应式处理,由于没有传入el,所以不会进行挂载 - 接下来,组件实例调用
$mount()方法child.$mount():入参 el 为空,由于vm.$el = el所以$el为空,对组件进行模板编译生成组件的 render 函数并缓存的到opts.render上,并执行组件的挂载mountComponent,相当于重新走了一遍new Vue,构建了一个组件的实例; - 这时,又会去走组件的
beforeCreate生命周期 hook,再去给组件创建一个watcher;
由于每个组件都会走一遍初始化流程,所以,每个组件都会有一个
watcher;
- 有了
watcher就会走子组件渲染,根据子组件创建一个虚拟节点vm._render()返回vnode
5,根据虚拟节点生成真实节点
- 通过 vm._update 对组件进行初始化渲染,赋值给 vm.el`,对应组件模板渲染后的结果;(第一次是 id=app,第二次是 null,因为 $mount 中没有传参 el,此时 patch 中的 oldVnode 为 null,此时说明是组件,创建出组件的真实节点)
- 通过
vnode.componentInstance = new Ctor()使后面可以拿到组件渲染后的结果:vnode.componentInstance.$el
6,将组件的 vnode.componentInstance.$el 插入到父标签中
7,在 new Ctor() 组件实例化时,会执行组件初始化流程,为组件添加独立的渲染过程;
- 每个组件实例都拥有独立的渲染
watcher; - 当组件渲染时,组件对应的属性会收集自己的渲染
watcher; - 当组件更新时,只需更新组件对应的渲染
watcher即可; - 所以,组件是局部更新的,性能也会比较好
四,结尾
本篇,组件部分-组件挂载流程简述;
下篇,组件部分-组件相关流程总结;
备注:还需要添加很多图,后续迭代。。。