上回咱们提到,在子组件存在的状况下,父组件在执行完
created
钩子函数以后生成子组件的实例,子组件执行created
钩子函数,同时也检查是否也有子组件,有则重复父组件的步骤,不然子组件的dom
元素渲染javascript
vnode
在上一篇文章中其实咱们提到一个函数 —— createComponentInstanceForVnode
👇前端
function createComponentInstanceForVnode ( vnode, // we know it's MountedComponentVNode but flow doesn't parent // activeInstance in lifecycle state ) {
var options = {
_isComponent: true,
_parentVnode: vnode,
parent: parent
};
// check inline-template render functions
var inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
return new vnode.componentOptions.Ctor(options)
}
复制代码
与之相关的代码👇java
...
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
复制代码
从中咱们能够得知:node
createComponentInstanceForVnode
生成的vnode.componentOptions.Ctor(options)
有关VNode
经过全局检索componentOptions
,可知存在以下代码👇浏览器
var VNode = function VNode ( tag, data, children, text, elm, context, componentOptions, asyncFactory ) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
...
}
复制代码
实际上,在beforeMount
钩子和mounted
钩子之间,有段奇怪的代码😅微信
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
复制代码
看过前面的文章的你其实已经知道Watcher
的执行逻辑:闭包
getter
属性value
赋值的同时执行getter
附updateComponent
实现:app
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
复制代码
这意味着函数 updateComponent
将被执行,同时存在这样的调用顺序(从上往下执行):dom
vm._render
vm_update
同时dom
元素确定也是在这两个函数调用时渲染async
vm._render
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentVnode = ref._parentVnode;
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
var 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.
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
的执行过程(从上往下):
_parentVnode
(父组件的 vnode
) 赋值给 vm.$vnode
normallizeScopedSlots
,将父子组件的 $slots
跟 $scopedSlots
合并render
函数并赋值给 vnode
(即获得现有的 vnode
)vnode
为空则执行 createEmptyVNode
函数vnode
这里咱们优先把断点打入 render
函数,理所固然的会获得如下执行过程:
render
vm.$createElement
因为最早执行的是 new Vue({...})
,因此看上去断点好像停在了「奇怪的地方」👇
new Vue({
render: h => h(App),
}).$mount('#app')
复制代码
render
函数细心的同窗会注意到这样一行代码👇
vnode = render.call(vm._renderProxy, vm.$createElement);
复制代码
步进以后断点立刻跳到了这里👇
new Vue({
render: h => h(App),
...
}).$mount('#app')
复制代码
其实,这里将 vm._renderProxy
做为了 render
函数的上下文对象,而 vm.$createElement
返回一个闭包函数做为 render
函数的参数传入
相关代码:
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
复制代码
总结下生成 vnode
的完整逻辑:
$mount
函数dom
元素并赋值给 el
变量,不然 el
变量取值 undefined
mountComponent
函数new Watcher(vm, updateComponent, noop, ...)
Watcher
的「特性」(传入的 updateComponent
赋值给 getter
以后执行),_render
函数在这以后会被触发$createElement
createElement
_createElement
data
及 data.is
是否不为空,是则将 data.is
赋值给 tag
tag
为空,那么认为这是一个空白节点,此时调用 createEmptyVNode
建立一个「空白节点」,并把 isComment
标记为 true
tag
是不是「保留字」,是则属于 HTML
标签,生成对应的 vnode
,不然调用 createComponent
函数生成对应的 vnode
vnode
createEmptyVNode
var createEmptyVNode = function (text) {
if ( text === void 0 ) text = '';
var node = new VNode();
node.text = text;
node.isComment = true;
return node
};
复制代码
扫描下方的二维码或搜索「tony老师的前端补习班」关注个人微信公众号,那么就能够第一时间收到个人最新文章。