这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战
Vue初次渲染核心流程大致为:解析模版生成render函数、调用render函数生成虚拟DOM、通过虚拟DOM创建真实DOM渲染至浏览器展示
核心流程
前文已经完成了render函数生成,接下来便是挂载的流程,首先修改$mount函数,增加mountComponent函数作为挂载的入口
/**
* 渲染流程
*/
Vue.prototype.$mount = function (_el) {
const vm = this;
const ops = this.$options;
const el = document.querySelector(_el);
if (!ops.render) {
// 不存在render函数, 需要将其进行编译
let template = ops.template;
if (!template && el) {
// 没有传递模版 且传递了el
template = el.outerHTML; // 将el的内容作为模版
}
// 将template 转换为 render
const { render } = compileToFunction(template);
//
ops.render = render;
}
// 挂载组件
mountComponent(vm, el);
};
初次渲染流程属于Vue生命周期内需要做的事情,将mountComponent函数抽取到lifecycle.js中,初始化函数
export function mountComponent(vm, el) {
const options = vm.$options;
vm.$el = el; // 真实的DOM元素
// todo ...
}
在这个函数中主要做的事情有两个
- 调用
render函数生成虚拟DOM - 通过虚拟DOM创建真实DOM渲染
_render
需要注意这里的_render内部调用的就是前文的render函数
Vue.prototype._render = function () {
const vm = this;
const { render } = vm.$options;
// render 去实例上取值, 返回 vnode
return render.call(vm);
};
_update
通过调用_render返回虚拟DOM,创建真实dom并渲染,重要的操作提取至patch函数中
Vue.prototype._update = function (vnode) {
const vm = this;
// 需要用虚拟节点创建出来真实节点 替换掉 真实的 $el
vm.$el = patch(vm.$el, vnode);
};
至此可得到mountComponent核心工作
vm._update(vm._render());
render
在调用render时,返回的数据结构
function render() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('p', [_v("hi " + _s(msg))]), _v(" hello")])
}
}
其中的这些_c、_v等函数,返回的便是虚拟DOM,接下来便看看这些函数的具体实现
将_c、_v、_s等函数的实现封装src/render.js中,通过在src/index.js中引用并执行
index.js新增两行代码
+++ import { renderMixin } from "./render"; // 引用
+++ renderMixin(Vue); // 执行
render.js中大体结构
export function renderMixin(Vue) {
/**
* _c 创建元素的虚拟节点
* _v 创建文本的虚拟节点
* _s JSON.stringify
*/
Vue.prototype._c = function () {};
Vue.prototype._v = function (text) {};
Vue.prototype._s = function (val) {};
}
创建虚拟DOM
_c和_v就是用来创建我们常说的虚拟节点,首先看一下创建虚拟DOM的方法vnode
const vnode = (tag, data, key, children, text) => {
return {
tag,
data,
key,
children,
text,
};
};
_c:用来创建元素的虚拟节点,其内部是根据createElement进行创建
/**
* 创建元素的虚拟节点
*/
const createElement = (tag, data, ...children) => {
let key = data && data.key;
if (key) {
delete data.key;
}
return vnode(tag, data, key, children, undefined);
}
const _c = function () {
return createElement(...arguments);
};
_v:用来创建文本的虚拟节点,其内部是根据createTextNode进行创建
/**
* 创建文本的虚拟节点
*/
const createTextNode = (text) => {
return vnode(undefined, undefined, undefined, undefined, text);
}
const _v = function (text) {
return createTextNode(text);
};
_s:其做的事情比较简单,进行字符串序列化
const _s = (val) => {
return val === null ? "" : typeof val === "object" ? JSON.stringify(val): val;
};
在Vue中拥有很多此类的方法,本文不做更多的解释,具体可以参考源码src/core/instance/render-helpers/index.js
export function installRenderHelpers (target) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
target._d = bindDynamicKeys
target._p = prependModifier
}
至此Vue的初次渲染的大致架子已经完成,也对其创建虚拟DOM的几个常用函数做了介绍,接下来就需要看看是如何通过虚拟DOM创建真实DOM并渲染至浏览器中