上篇我们说到mount
方法里面有个关键方法mountComponent
,里面的渲染watcher执行的updateComponent
代码如下:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
本次就来说说这个_render做了什么
前提知识
render:字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode。
![](https://img.haomeiwen.com/i7792325/56f657d814f5237a.png)
Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)
let p = new Proxy(target, handler);
Proxy简单说来就是利用handler做对target各种操作的代理,更具体的可以自行去MDN了解一下。
_render
调试代码
import Vue from 'vue';
import App from './App.vue';
Vue.config.productionTip = false;
var app = new Vue({
el: '#app',
components: {
App,
},
render: h => h(App),
data() {
return {
textHi: 'hi',
};
},
});
// src/core/instance/render.js
Vue.prototype._render = function (): VNode {
const vm: Component = this
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 because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
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
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 = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
这个_render
函数执行中,通过
vnode = render.call(vm._renderProxy, vm.$createElement)
得到一个vnode对象,这里的render内容如下图:
![](https://img.haomeiwen.com/i7792325/cbbc30c780a71a09.png)
render函数可以通过用户定义,也可以通过编译生成
initRender&createElement
这个_c是在initRender
里被定义的
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
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
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)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
我们可以看initRender里定义了vm._c
和vm.$createElement
,他们其实作用都是调用createElement
生成,不一样的是_c是编译生成的render函数里用的,而$createElement
为用户设置的render函数里用的。
至于为什么render函数内部是这样的可以参考createElement参数
![](https://img.haomeiwen.com/i7792325/b8441c324d22a783.png)
_renderProxy
那么vm._renderProxy
是什么呢?
在initProxy里定义了实例的_renderProxy ,在这里对vm的操作做了劫持,如果没有在方法或者data里定义了要访问的值,则会调用warn
在控制台会出现警告
// src/core/instance/proxy.js
initProxy = function initProxy (vm) {
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
}
}
回到_render
...
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
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 = createEmptyVNode()
}
...
接下来判断了一次得到的vnode是否为数组,如果是则取数组里第一个
再判断vnode是否为VNode的实例,如果不是则说明当前存在多个根节点。比如说我们在组件里写了两个根节点,就会看到这个错误。
网友评论