跟着黄轶大神学习的 vue,做一些简单记录。
import Vue from 'vue'
import App from './App.vue'
var app = new Vue({
el: '#app',
// 这里的 h 是 createElement 方法
render: h => h(App)
})
这里的逻辑和数据驱动中的大致逻辑是一样的,都是在做初始化,到了下一步在会有所区别
vue/src/core/instance/lifecycle.js _update方法中的updateComponent
vm._render()
要生成 App 组件的 vnode,vm._update()
要将组件 vnode 生成 dom 渲染载页面上,和数据驱动中的略有不同vue/src/core/vdom/create-element.js
_createElement()
方法,与 不同的是,这里的tag
是个组件,所以走 else 逻辑,执行 createComponent
...
if (typeof tag === "string") {
...
else {
vnode = createComponent(tag, data, context, children);
}
...
createComponent
影响关键流程的是以下三部分
const baseCtor = context.$options._base;
Ctor = baseCtor.extend(Ctor);
这里的baseCtor
其实就是指的 Vue,也就是执行了Vue.extend()
方法,extend 方法会返回一个 Sub 构造函数,其逻辑和 Vue 构造函数基本一致(某种程度上可以把它当作 Vue,只在细微之处有区别,是继承 Vue 的),所以最后 Ctor 就是 Sub(一个继承 Vue 的构造函数)
// vue/src/core/global-api/extend.js
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
这里会往 data.hook 里注入几个 hook,其中的init hook
将会发挥重大作用
生成一个组件 vnode 并返回,这里和普通 vnode 的区别是 vnode 的属性存放的地方不一样,组件 vnode 都放在.componentOptions 上
vue/src/core/instance/lifecycle.js Vue.prototype._update
vm._update
就是需要根据 vnode 生成 html 元素并挂载到页面上,实际执行的就是__patch__
方法vue/src/core/vdom/patch.js patch
createElm
,再进入createComponent
中createElm
之后也直接执行 return 结束整个createElm
// fn createComponent
let i = vnode.data;
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */);
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true;
}
}
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode, // 这里就是组件vnode
parent, // 当前的new Vue的实例vm
};
...
const child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
));
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
vue/src/core/vdom/create-component.js init
createComponentInstanceForVnode 主要是return new vnode.componentOptions.Ctor(options)
,这里的vnode.componentOptions.Ctor
就是 3.3 中返回的 Sub 构造函数,所以 createComponentInstanceForVnode 也就是这里的代码可以理解为return new Sub(options)
,这里也就是相当于初始化 Sub 实例了(但由于 vm. o p t i o n s . e l 是 u n d e f i n e d , 所 以 不 会 走 options.el是undefined,所以不会走 options.el是undefined,所以不会走mount),值得一提的是这里的 option 和数据驱动的不太一样的地方是,多了三个属性_isComponent、_parentVnode、parent
,我理解只有组件 vnode 才会有这三个属性,这是为了组件元素和其内部进行连接、通信
child.$mount
和数据驱动过程基本一样,但不同点在于child.$mount
并没有挂载到页面上,而是记录在 vnode.elm 中(insert 的 parent 参数为 undefined)
// fn createElm => insert
function insert(parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) {
nodeOps.insertBefore(parent, elm, ref);
}
} else {
nodeOps.appendChild(parent, elm);
}
}
}
从以上可以看出 init 其实就是在初始化 Sub 实例
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
这里共执行 2 个函数,initComponent 的关键流程是:
vnode.elm = vnode.componentInstance.$el;
这里的 vnode.componentInstance.$el 就是前面组件初始化生成的 html 元素
// _update方法
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
// __patch__方法返回的就是vnode.elm
此流程过后组件 vnode.elm 就有了该组件生成的 html 元素,接下来的 insert 就会将这段 html 元素挂载到页面上,createElm 函数整体结束
到这里整个渲染过程就结束了
mygithub