vue2.0源码解析,initRender(初始化render)

注:此章只讨论初始化过程,对render具体渲染不做讨论,在后面会有文章解析render过程。

initRender函数

/*初始化render*/
export function initRender(vm: Component) {
	// _vnode 组件的真实节点,它的tag就是<template>标签下的第一个节点
	vm._vnode = null; // the root of the child tree
	vm._staticTrees = null;

	// $vnode  _parentVnode: 父树中的占位符节点,它的tag就是在父组件中的组件名 $vnode可以说是_vnode的父级
	const parentVnode = (vm.$vnode = vm.$options._parentVnode); // the placeholder node in parent tree  父树中的占位符节点

	// 父组件的vm实例
	const renderContext = parentVnode && parentVnode.context;
	
	// 储存着父组件传来的slot
	vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext);

	// 这里调用emptyObject Object.freeze({}) 冻结只是当做一个空值,空对象与我们平常的null差不多
	vm.$scopedSlots = emptyObject;

	// bind the createElement fn to this instance
	// so that we get proper render context inside it.
	// args order: tag, data, children, normalizationType, alwaysNormalize
	// internal version is used by render functions compiled from templates
	/*将createElement函数绑定到该实例上,该vm存在闭包中,不可修改,vm实例则固定。这样我们就可以得到正确的上下文渲染*/
	vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
	// normalization is always applied for the public version, used in
	// user-written render functions.
	/*常规方法被用于公共版本,被用来作为用户界面的渲染方法*/
	vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);
}

initRender函数里的代码不多,寥寥几行,但里面要理清楚还是有很多东西要理解。


分析

_vnode与$vnode

初看时,这两个命名只差一个符号,所以很容易将读者搞混,我们可以输出一个例子来帮助我们弄懂这两个变量。

这里需要注意的是_vnode变量在created的时候是为null,只有去到mounted即挂载组件后才有值。
我们可以先看下这两个对象打印出来的结果

// app
<div id="app">
     <child></child>
 </div>

// child
<div class="child-wrap">child组件</div>

export default {
    data() {
        return {

        }
    },
    mounted() {
        console.log('child组件_vnode', this._vnode);
        console.log('child组件$vnode', this.$vnode);
    }
}

_vnode与$vnode

  • 两个对象都是vnode类型对象
  • _vnode的tag是div,在初始化的时候为null,并且elm指向child的顶级标签,并且在自身组件挂载前(mounted前)不存在,说明这是它就是组件的vnode对象。
  • $vnode的tag是vue-component-5-child,因为我是用了vue-template-compiler编译模式,所以在编译的时候会将child编译,编译前:<child></child>, 编译后:<vue-component-5-child></vue-component-5-child>,在初始化的时候通过$options._parentVnode传入,并且自身组件挂载前(mounted前)存在,父组件挂载前(mounted前)不存在,$vnode的指向是从组件在父节点的占位时的节点。
  • 观察上面两个对象得知,$vnode的指向是子组件在父节点的占位时的节点,_vnode是它就是组件的vnode对象

parentVnode、renderContext
const parentVnode = (vm.$vnode = vm.$options._parentVnode); 

const renderContext = parentVnode && parentVnode.context;

这一段代码是找到组件的slot传入,做保存。
vm.$vnode = vm.$options._parentVnode,$vnode在上面讲过,所以不做二次解释了。
parentVnode.context,先说结论,这个变量其实就是父组件的vnode,而它也等于子组件的 $parent,直接放图。

// child组件
console.log(this.$options._parentVnode.context === this.$parent);
console.log(this.$options._parentVnode.context);

console


resolveSlots函数

resolveSlots主要是找出当前组件下在父组件传入的slot,并且返回;
children是一个vnode列表,子组件下一级所有的vnode.
context是传入组件的父组件实例,用于确定的slot的时候对比当前的上下文
函数的作用是循环传入的vnode,将有slot属性的值放到对应的slot,没有的放到default

这里有一个注意点:当标签组有空格也会被当成一个文本vnode,所以这里做了处理,当只有一个空格的节点时,直接忽略

//rerdner.js
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext);

// resolve-slots.js
/**
 * Runtime helper for resolving raw children VNodes into a slot object.
 */
export function resolveSlots(children: ?Array<VNode>, context: ?Component): { [key: string]: Array<VNode> } {
	const slots = {};
	if (!children) {
		return slots;
	}
	const defaultSlot = [];

	// 循环传入的slot,根据带有或不带slot属性,区分成对应的slot储存
	for (let i = 0, l = children.length; i < l; i++) {
		const child = children[i];
		// named slots should only be respected if the vnode was rendered in the
		// same context.
		
		// child.context === context || child.functionalContext === context: 判断是否来自同一个域
        // child.data 标签是否有属性值 <div class="aaa" slot="header"></div>
		if ((child.context === context || child.functionalContext === context) && child.data && child.data.slot != null) {
			const name = child.data.slot;

			// slots.传入的slot名称
			const slot = slots[name] || (slots[name] = []);

			if (child.tag === "template") {
				// 忽略template标签
				slot.push.apply(slot, child.children);
			} else {

				slot.push(child);
			}
		} else {
			// 没有当做默认slot defaultSlot
			defaultSlot.push(child);
		}
	}

	// 不是一个注释或者只有一个空格的文本节点
	if (!defaultSlot.every(isWhitespace)) {
		slots.default = defaultSlot;
	}
	return slots;
}

// 是否为注释或者只有一个空格的文本节点
function isWhitespace(node: VNode): boolean {
	return node.isComment || node.text === " ";
}

最终生成实例的$slot如下

<child>
    <div class="slot-defalt-1">aaaaa</div>
    <div class="slot-defalt-2">bbbbb</div>
    <div slot="ccc">cccccc</div>
</child>

$slot

_c与$createElement

ps: 这里对createElement里面函数生成原理不做讨论,因为在这个阶段只是赋值,而非运行renderVnode,后面有专门讲这里。

// a: 标签
// b: 标签属性
// c: children
// d: normalizationType(是否需要额外兜底处理)
vm._c = (a, b, c,d) => createElement(vm, a, b, c, d, false);

vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);

最后两行代码,在这里赋值创建了_c以及$createElement函数,多将当前vm绑定到第一个参数,唯一的区别只有最后一个参数alwaysNormalize不同。
它们都是生成vnode的方法,_c 用于从模板<template>编译得到的组件中使用,通常是vue内部使用生成vnode,而$createElement是放给用户用作render渲染写法。
而$createElement作为公共函数,具有一定开放性,下面有几个例子,用户可能会传入不同的children,比如文本,数字,数组,单一子节点,所以需要去处理不同的情况。

// vue 的render写法
render: function (createElement) {
    return createElement(
        'h1',   // tag name 标签名称
        {},
        '这是显示文本'
    )
},

// 或者文本是第二个参数
render: function (createElement) {
    return createElement(
        'h1',   // tag name 标签名称
        '这是显示文本'
    )
},

// 子节点
render: function (createElement) {
    return createElement(
        'h1',   // tag name 标签名称
        {},
        [createElement('p', 'hi'), createElement('p', '你好')]
    )
},

vue官方文档-createElement

到这里initRender基本已经解释完毕,已经将我看源码时的疑问写在上面,如有其他疑问不明白,可留言或私信,我将补充到博客中。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值