【Vue原理】Render - 源码版 之 主要 Render

专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧

研究基于 Vue版本2.5.17

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 挂载

最后

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值