Vue之vnode创建
前言上一篇文章中分析了html->vnode主要的处理过程,实际上主要是parse + generate解析template构建render函数的过程,实际上vnode的创建是在render执行过程中触发的。本文主要分析render调用过程以及该过程vnode的创建。具体分析在render构建和调用那篇文章中,就提及render的调用实际上是Watcher实例对象创建
前言
上一篇文章中分析了render函数生成的主要细节,实际上主要是parse + generate解析template构建render函数的过程。当render函数构建过后,接下来就生成一个watcher实例,该实例与视图渲染密切相关,会触发render函数执行。
实际上VNode对象的创建是在render调用过程中触发的,这是render函数本身的结构导致的。本文主要分析render调用过程中vnode的创建。
具体分析
在render构建和调用那篇文章中,就提及render的调用实际上是Watcher实例对象创建触发的,主要的处理逻辑如下:
从上图的主要逻辑点可以看出,$mount的处理过程中主要会构建Watcher实例,而Watcher实例最终会执行Vue实例方法_update和_render,这里是核心了。
_render实例方法
_render实例方法实际上主要的处理点就是执行render函数返回其执行结果,核心代码逻辑如下:
var vnode = render.call(vm._renderProxy, vm.$createElement);
return vnode;
在上一篇文章中,知悉了render函数的基本构造,还使用如下实例:
<div>
{{ text }}
</div>
得到的render函数如下:
const render = new Function("with(this) {return _c('div', {attrs: {'id': 'app'}}, [_v('\n' + _s(text) + '\n)])}")
在执行_render函数实际上就是执行render函数,此时会调用:
vm._c:即$createElement
vm._s:即toString函数
vm.text:即触发data响应式,会调用getter函数,获取text最新的值
_s实例方法
_s实例方法的源码如下:
function toString(val) {
return val === null
? ' '
: typeof val === 'object'
// 格式化对象,并指定缩进为2个空格
? JSON.stringify(val, null, 2)
: String(val);
}
主要就是处理null、对象以及数组形式的数据等将其转换为字符串。
_v实例方法
function createTextVNode(val) {
return new VNode(undefined, undefined, undefined, String(val));
}
_v实际上就是createTextVNode函数,用于创建文本类型的虚拟DOM
_c实例方法
该方法是vnode创建的实际出发点,Vue核心方法之一,具体源码如下:
vm._c = function(a, b, c, d) {
return createElement(vm, a, b, c, d, false);
};
createElement整个的处理逻辑如下:
上图是根据最基本的实例梳理的主要脉络,核心是调用VNode构造函数创建虚拟DOM:
new VNode(tag, data, children, undefined, undefined, context)
实际上HTML标签、SVG标签、自定义组件都是通过createElement来生成对应的Vnode对象的。
这里需要说明下createElement中a、b、c、d参数表示的含义:
- a:tag,表示标签名
- b:data,表示属性、事件、class、props等的配置对象
- c: children,表示子节点
- d:normalizationType,表示类型,即要如何处理children中的数据
实际上对于自定义组件这里的逻辑就需要具体聊聊了,creaElement关于这边的逻辑如下:
if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag);
}
而createComponent函数的逻辑自然是处理与组件相关的属性例如props、事件等从而创建Vnode对象,但是其中有一个重要的逻辑是installComponentHooks。
自定义组件对应的虚拟节点VNode对象有一些Hooks(钩子函数):
var componentVNodeHooks = {
init: function init() {},
prepatch: function prepatch(oldVnode, vnode) {},
insert: function insert(vnode) {},
destroy: function destroy(vnode) {}
};
实际上定义初始化、patch之前、插入、销毁几个阶段的钩子,这些钩子是用于patch阶段用来创建、更新、销毁组件的。
实际上这里还隐藏了一个非常重要的逻辑信息:
所有通过createElement创建的组件都是子组件,而所有子组件在render函数执行时只是创建了虚拟节点VNode对象而已。每一个组件都是一个Vue实例,子组件的Vue实例创建是在patchVnode(即diff算法)阶段才生成的
VNode构造函数
VNode构造函数实际上就是定义相关属性,VNode中重要的属性有:
- tag:当前标签名
- data:标签属性、props、事件等对象集合
- children:子节点的VNode数组
- text:当前标签文本内容
- context:上下文对象,即Vue实例对象
var VNode = function(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
this.tag = tag;
this.data = data;
this.children = children;
...
}
简单实例_render函数的处理就是上面整个逻辑:
_render() -> render -> createElement(或createTextVNode)-> new VNode
_render执行render函数生成DOM结构的vnode,接下来就是_update实例方法的调用了。
_update实例方法
_update中处理实际上有两个主要点:
- vm._vnode相关处理,两点:prevNode = vm._vnode,vm._vnode = vnode
- vm.__patch__的调用
prevNode记录更新前的vnode,如果是初始化,那么prevNode就是空,调用__patch__实现vnode -> html的过程,也是diff算法的实现过程,是整个Vue中核心点之一。
_update核心源码如下:
var prevNode = vm._vnode;
vm._vnode = vnode;
if (!prevNode) {
// 初始化
vm.$el = vm.__patch__(vm.$el,vnode, hydrating, false);
} else {
// 更新
vm.$el = vm.__patch__(prevNode, vnode);
}
总结
render函数的结构:
with(this) {
// code
}
this === Vue实例,这个结构内部对相关属性的使用都会从Vue实例中查找,而Vue响应式就是通过属性触发的。
render函数执行过程中_v、_c函数调用会触发组件或原生标签对应虚拟节点VNode的生成,而响应式data数据也是render函数调用触发的。而_update实例函数就负责基于VNode创建真实DOM,通过diff算法最大优化DOM复用。
更多推荐
所有评论(0)