使用vue 这么长时间,对vue 对源码了解还是不多,作为一个不甘平庸的前端小白,决定奋起,那么今天我们就来谈谈vue 的render 函数;
打开源码,我发现render 函数返回一个VNode; 可是我们并未在模版中写render 呀,这又是一个什么样的过程呢?
当我们创建一个Vue 实例的时候,我们会把template 编译生成render 函数;render 函数返回一个VNode; vue/src/core/instance/render.js
// 返回一个VNode;方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node。
Vue.prototype._render = function (): VNode {
const vm: Component = this
//拿到render 函数,可以是用户自己写,也可以通过编译生成;
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack becaues all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
//不需要维护堆栈,因为所有render 函数都是单独调用的。当修补父组件时,将调用嵌套组件的呈现FN
currentRenderingInstance = vm
// 第一个参数是当前上下文,_renderProxy 是在initProxy 中定义的,第二个是
//vm.renderProxy 在生产环境下就是vm,在开发环境可能是一个proxy 对象;vm.$createElement 在initRender 函数中有定义;
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
// 如果渲染函数出错,则返回一个空的VNode;
// 如果vnode 不是VNode 的一个实例;
if (!(vnode instanceof VNode)) {
// vnode 是一个array 说为vnode 是多个根节点;VNode 虚拟DOM;
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
// 从render 函数中返回多个根节点,应该返回单个的根节点;
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
那么我们手写render,跟使用编译的有何区别?
编译生成render 函数
index.html’
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>vue-analysis</title>
</head>
<body>
<div id="app">
{{message}}
</div>
<!-- built files will be auto injected -->
</body>
</html>
main.js’
import Vue from 'vue'
/* eslint-disable no-new */
new Vue({
el: '#app',
data() {
return {
message: 'hello vue'
}
}
})
手写render 函数
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>vue-analysis</title>
</head>
<body>
<div id="app">
</div>
<!-- built files will be auto injected -->
</body>
</html>
main.js
import Vue from 'vue'
/* eslint-disable no-new */
new Vue({
el: '#app',
render(createElememt) {
return createElememt('div',{
attrs: {
id: "#app1"
}
}, this.message)
},
data() {
return {
message: 'hello vue'
}
}
})
总结:
// 与直接在html中写不一样,她没有从插值变换过来的过程,之前我们是在html 中定义了插值,她在不执行的时候,先把html 的空文件渲染出来,然后在new Vue 之后,执行mounted 方法再把插值从message 中替换成真实的数据,
// 我们通过render 函数,直接手写render,我们不用在页面上显示那个插值,而是通过render函数,当它执行完毕以后,会把我们的message 替换上去,这样体验会更好一些;这里我们手写了render函数,我们就没有把template 转换成render函数这一步了;
//注意,我们挂载的元素会替换掉我们的根节点,这也是我们为什么不能用body 作为挂载点的原因,会把body 替换掉;
职场小白south Joe,望各位大神批评指正,祝大家学习愉快!