这是我参与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并渲染至浏览器中