写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue 版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面 关注公众号 也可以吧
【Vue 原理】Render – 源码版 之 主要 Render
compile 我们已经讲了九篇的内容了,终于走到了 render,今天就来给自己记录下渲染三部曲的第二部,render,咦,render 内容不多的,就两篇文章哈哈哈
噔噔噔噔
render 的作用大家应该清楚
就是 执行 compile 生成的 render 函数,然后得到返回的 vnode 节点
比如现在存在这个简单的模板
经过 compile 之后,解析成了对应的 render 函数,如下
function render() {with(this) {
return _c('div', {
attrs: {"data": 111}
},
[_v(111)])
}
}
看着这个莫名其妙的 render 函数,里面都是些什么东西?
不怕,主要是出现了两个函数,我们要探索的就是这两个东西
_c , _v
这个两个函数的作用,都是创建 Vnode,但是创建的过程不一样
并且 render 函数执行的时候,会绑定上 模板对应的实例 为上下文对象
模板是属于哪个实例的,就绑定哪个实例
render.call(实例)
再通过 with 的作用
调用 _c 和 _v 就相当于 vm._c 和 vm._v
什么是 vm._v
现在就来看看 vm._v 是哪里来的
function installRenderHelpers(target) {target._v = createTextVNode;}
installRenderHelpers(Vue.prototype);
由上面可知,每个 Vue 实例都会继承有 _v 这个方法,所以可以通过 vm._v 直接调用
再来看看 _v 对应的 createTextVNode 的作用是什么
创建文本节点!!
看下源码
function createTextVNode(val) {
return new VNode(
undefined, undefined,
undefined, String(val)
)
}
比如这个模板
{{data}} 虽然是字符串,但是也要作为一个子节点存在,所以就当做是 文本节点
而 data 的值是 111
然后 上面的模板就会得到这样的 Vnode 结构如下
什么是 vm._c
_c 是一个大头,render 的重中之重,先来看看他是怎么来的
function initRender(vm) {vm._c = function(a, b, c, d) {return createElement(vm, a, b, c, d);
};
}
Vue.prototype._init = function(options) {initRender(this)
}
在实例初始化的时候,就会给实例绑定上 _c 方法
所以,vm 可以直接调用到 _c
看了上面的源码,看到 _c 内部调用了 createElement
那就来看看 createElement 的源码吧
个人已经简化得非常简单,觉得不偏离我们的主题就可以
function createElement(context, tag, data, children) {
return _createElement(context, tag, data, children)
}
function _createElement(context, tag, data, children) {
var vnode;
if (如果 tag 是正常 html 标签) {
vnode = new VNode(
tag, data, children,
undefined, undefined,
context
);
}
..... 如果 tag 是组件名,就特殊处理,处理流程已经省略
if (Array.isArray(vnode))
return vnode
else {
// ... 动态绑定 style,class,代码已经省略
return vnode
}
}
你一看就可以看到,createElement 主要就是调用了 new VNode,当然了,render 就是为了创建 vnode 的嘛
你在前面也看到了 render 函数,有传了很多参数给 _c,如下,_c 再把这些参数传给构造函数 VNode
_c('div',
{attrs: {"data": 111}
},
[_v(111)]
)
上面这些参数都会传给 Vnode,并保存在创建的 Vnode 中
function VNode(tag, data, children, text) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
}
然后得到这么一个 Vnode
{
tag:"div",
data:{attrs: {"data": 111}
},
children:[{
tag:undefined,
data:undefined,
text:111
}]
}
说到这里,已经能很清楚 render 内部是如何创建 Vnode 了
但是这里只是其中一种小小的简单 render
要是项目中的 render,数据是很多,很复杂的
而我们主要要把握的是主要流程就可以了
不过,还有必要记录其他 render,那就是遍历
遍历相关
看下面这个 template
解析成下面的 render
function render() {with(this) {
return _c('div',
_l(2,function(item, index) {return _c('span')
})
)
}
}
看到一个 _l,他必定就是遍历生成 Vnode 的幕后黑手了
同样的,_l 和 _v 在同一个地方 installRenderHelpers 注册的
function installRenderHelpers(target) {target._l = renderList;}
不客气地搜出 renderList 源码出来
先跳到后面的分析啊,源码有点长了,虽然很简单
function renderList(val, _render) {
var ret, i, l, keys, key;
// 遍历数组
if (Array.isArray(val) ) {ret = new Array(val.length);
// 调用传入的函数,把值传入,数组保存结果
for (i = 0, l = val.length; i < l; i++) {ret[i] = _render(val[i], i);
}
}
// 遍历数字
else if (typeof val === 'number') {ret = new Array(val);
// 调用传入的函数,把值传入,数组保存结果
for (i = 0; i < val; i++) {ret[i] = _render(i + 1, i);
}
}
// 遍历对象
else if (typeof val =="object") {keys = Object.keys(val);
ret = new Array(keys.length);
// 调用传入的函数,把值传入,数组保存结果
for (i = 0, l = keys.length; i < l; i++) {key = keys[i];
ret[i] = _render(val[key], key, i);
}
}
// 返回 vnode 数组
return ret
}
看到 renderList 接收两个参数,val 和 render,而 _l 调用的时候,也就是传入的这两个参数,比如下面
_l(2,function(item, index) {return _c('span')
})
val 就是 2,_render 就是上面的函数
1 遍历的数据 val
遍历的数据分为三种类型,一种是对象,一种是数字,一种是数组
2 单个 vnode 渲染回调 _render
重要是这个回调
1、renderList 每次遍历都会执行回调,并把的每一项 item 和 index 都传入 回调中
2、回调执行完毕,会返回 vnode
3、使用数组保存 vnode,然后 遍历完毕就返回 数组
于是可以看上面的 render 函数,传入了 数字 2,和 创建 span 的回调
_l(2,function(item, index) {return _c('span')
})
_l 执行完毕,内部遍历两次,最后返回 两个 span vnode 的数组,然后传给外层的 _c,作为 vnode.children 保存
render 执行完毕,得到这样的 vnode
{
tag:"div",
data:undefined,
children:[{
tag:"span",
data:undefined
},{
tag:"span",
data:undefined
}]
}
都灰常简单啊,没写之前,我还觉得内容应该挺多的,写完发现还可以
当然还有其他的 render,但是我都已经在其他文章中有详细的记录了,可以直接点连接观看
比如要模板含有 filter
Filters – 源码版
比如要模板含有 普通 slot
Slot – 源码版之普通插槽
比如要模板含有 作用域 slot
Slot – 源码版之作用域插槽
现在我们来解决一个问题
render 什么时候开始执行?
可以参考另一篇文章:
从模板到 DOM 的简要流程
总结
每个模板经过 compile 都会生成一个 render 函数
render 作为 渲染三部曲的第二部,主要作用就是 执行 render,生成 Vnode
把 template 上绑定的数据,都保存到 vnode 中
然后,生成 Vnode,就是为了给 渲染三部曲的 第三部 Diff 提供源动力
从而完成 DOM 挂载