准备
vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。
回顾
如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》
_render的定义
Vue原型上_render
定义在core/instance/render.js
,这个文件随着core/instance/index.js
中renderMixin(Vue)
的调用而加载。
以下是render.js
文件。(剔除了非主线分代码,下同)
/** core/instance/render.js **/
//在Vue原型上挂载私有的_render方法
Vue.prototype._render = function (): VNode {
//从options中获取用户自己定义的render方法
const { render, _parentVnode } = vm.$options;
let vnode;
try {
currentRenderingInstance = vm;
/**
* 调用用户自己定义的render方法,传入 vm.__renderProxy,vm.$createElement,获得一个vnode
* __renderProxy:生产环境为vm,也就是this,开发环境为一个Proxy对象(见"./init.js")
* $createElement:见同文件的 initRender() 函数
*/
vnode = render.call(vm._renderProxy, vm.$createElement)
}
}
这段代码主要是调用了用户传入的render
函数,传入了两个参数,一个是_renderProxy
,另一个是一个$createElement()
方法,这个函数返回了一个VNode
类型的值。我们先来看下_renderProxy
。
_renderProxy
_renderProxy
定义在core/instance/init.js
这里做了一个判断:
- 如果是开发环境的Vue,就执行一个
initProxy
的函数,传入Vue实例。 - 如果是生产环境的Vue,_renderProxy就是Vue实例
this
/** core/instance/init.js **/
/**
* 如果是开发环境,_renderProxy是一个代理
* 如果是生产环境,_renderProxy就是this
*/
if (process.env.NODE_ENV !== "production") {
initProxy(vm);
} else {
vm._renderProxy = vm;
}
initProxy
我们进入initProxy
看一下究竟这个_renderProxy
是个啥?
initProxy
定义在core/instance/proxy.js
。
这边其实是对Vue的实例vm
也就是this
做了一个数据劫持proxy
。
需要一些es6的proxy的知识。
/** core/instance/proxy.js **/
/**
* 开发环境下初始化vm._renderProxy
* 对对象访问进行劫持
*/
initProxy = function initProxy(vm) {
/**
* 是否兼容es6的proxy
*/
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options;
const handlers =
options.render && options.render._withStripped
? getHandler
: hasHandler;
vm._renderProxy = new Proxy(vm, handlers);
} else {
vm._renderProxy = vm;
}
};
这里的proxy
定义了handlers
,我们去看看getHandler
。
/** core/instance/proxy.js **/
const getHandler = {
get(target, key) {
if (typeof key === "string" && !(key in target)) {
if (key in target.$data) warnReservedPrefix(target, key);
else warnNonPresent(target, key);
}
return target[key];
},
};
好像是对数据的get
操作做了一些判断。我们去看看warnNonPresent
/** core/instance/proxy.js **/
onst warnNonPresent = (target, key) => {
warn(
`Property or method "${key}" is not defined on the instance but ` +
"referenced during render. Make sure that this property is reactive, " +
"either in the data option, or for class-based components, by " +
"initializing the property. " +
"See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.",
target
);
};
啊这,这不是……我们经常会报的错误吗?这里什么意思我就不用说了吧?
这下我们差不多搞懂了_renderProxy()
了。该回到_render()
了去看看$createElement()
;
$createElement
那这个$createElement
又是何方神圣呢?这个$createElement
其实就是我们手写render(h){ ... }
函数所需要的参数h
,在render.js
文件的顶部可以找到相关的定义。
这里有两个调用,一个是$createElement
,另外一个vm._c
是个Vue内部自身调用的(用于模板编译后的render()
)。
我们这里的render
不是template
编译后的render
,是用户自己手写的render
,所以我们接下来要用vm.$createElement
。
vm.$createElement
调用了一个createElement()
的函数,由于这个函数也是一个需要详细解读的部分,具体放在下一篇createElement的执行过程中讲解。
/** core/instance/render.js **/
export function initRender(vm: Component) {
//template编译出的render
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
//用户自定义的render
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);
}
好,我们回到_render()
:
这里调用$createElement
后得到一个VNode
,然后对这个VNode
做了两波判断,最后直接return vnode;
/** core/instance/render.js **/
vnode = render.call(vm._renderProxy, vm.$createElement);
//如果vnode是一个数组且长度为1,赋值数组的第一个元素给vnode
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0];
}
//当vnode不是一个VNode类型且vnode是一个数组的时候,警告:只能有一个根节点!
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== "production" && Array.isArray(vnode)) {
warn(
"Multiple root nodes returned from render function. Render function " +
"should return a single root node.",
vm
);
}
//创建一个空VNode给vnode
vnode = createEmptyVNode();
}
return vnode;
至此,render
的执行过程阅读完毕