前言
跟着黄轶大神学习的 vue,做一些简单记录。
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
流程图
步骤
1 初始化
位置:
vue/src/platforms/web/entry-runtime-with-compiler.js
vue/src/platforms/web/runtime/index.js
vue/src/core/index.js
vue/src/core/instance/init.js
初始化主要是为后续的流程提供支撑,包括以下几块的内容
- 参数处理,比如 mixin, extend,props
- 执行生命周期,比如 beforeCreate、created
- 生成内部的一些属性和方法,便于后续操作
这里涉及的内容较多,在后面用到的地方再提及,初始化完成之后就会进行挂载
// vue/src/core/instance/init.js
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
2 编译
位置: vue/src/platforms/web/entry-runtime-with-compiler.js
主要流程:
// 1. 保存真正的$mount方法
const mount = Vue.prototype.$mount;
// 2. 编译生成render staticRenderFns 方法,挂载到vm上
const { render, staticRenderFns } = compileToFunctions(...);
options.render = render;
options.staticRenderFns = staticRenderFns;
// 3. 调用真正的$mount方法,开始挂载
return mount.call(this, el, hydrating);
如果是运行时的版本是不需要这一步的,编译的最终目的就是生成render、staticRenderFns,支持$mount过程
3 挂载
$mount 定义在vue/src/platforms/web/runtime/index.js,最终调用的是mountComponent
mountComponent位置vue/src/core/instance/lifecycle.js
// 1. 执行beforeMount钩子
callHook(vm, "beforeMount");
// 2.定义方法
updateComponent = () => {
vm._update(vm._render(), hydrating);
};
// 3. 初始化渲染Watcher
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate");
}
},
},
true /* isRenderWatcher */
);
//4. 执行mounted钩子
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, "mounted");
}
可以看出主要是在 new Watcher 的过程中完成了挂载,再看 Watcher
位置: vue/src/core/observer/watcher.js
export default class Watcher{
constructor() {
...
// 这里的expOrFn就是上面传的updateComponent
this.getter = expOrFn;
...
this.value = this.lazy ? undefined : this.get();
}
get() {
...
try {
value = this.getter.call(vm, vm);
}
}
}
可以看出 new Watcher()中由于 this.lazy 是 undefined,会执行updateComponent,也就是说完成全部挂载任务的就是updateComponent
4 vm._render
位置:vue/src/core/instance/render.js
let vnode;
try {
vnode = render.call(vm._renderProxy, vm.$createElement);
}
return vnode;
可以看出 render()的主要作用是生成 vnode 并返回,主要执行的是步骤 2 中生成的render,从这里我们可以知道 render 会调用vm.$createElement
位置:vue/src/core/vdom/create-element.js
createElement 会根据参数的不同进行不同的处理,最终都是为了生成 vnode 并返回
vnode = new VNode(
config.parsePlatformTagName(tag),
data,
children,
undefined,
undefined,
context
);
return vnode
5vm._update
位置vue/src/core/instance/lifecycle.js
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
_update执行__patch__
位置:vue/src/core/vdom/patch.js
patch 的关键流程是执行 createElm:
const oldElm = oldVnode.elm;
const parentElm = nodeOps.parentNode(oldElm);
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
...
// 删除旧节点
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0);
}
createElm 的主要流程如下:
// 1. 生成div
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode);
// 2. 执行invokeCreateHooks ,把vnode里的各种数据转化为html元素的各种属性
invokeCreateHooks(vnode, insertedVnodeQueue);
function invokeCreateHooks(vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode);
if (isDef(i.insert)) insertedVnodeQueue.push(vnode);
}
}
// 3. 插入到页面上
insert(parentElm, vnode.elm, refElm);
createElm 执行完成之后,也就可以看到一个正常的页面了