共计 4148 个字符,预计需要花费 11 分钟才能阅读完成。
前言
上回我们提到,在子组件存在的情况下,父组件在执行完
created
钩子函数之后生成子组件的实例,子组件执行created
钩子函数,同时也检查是否也有子组件,有则重复父组件的步骤,否则子组件的dom
元素渲染
深入了解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) | |
} |
与之相关的代码????
... | |
var child = vnode.componentInstance = createComponentInstanceForVnode( | |
vnode, | |
activeInstance | |
); | |
child.$mount(hydrating ? vnode.elm : undefined, hydrating); |
从中我们可以得知:
- 子组件的实例是由
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
实现:
updateComponent = function () {vm._update(vm._render(), hydrating); | |
}; |
这意味着函数 updateComponent
将被执行,同时存在这样的调用顺序(从上往下执行):
vm._render
vm_update
同时 dom
元素肯定也是在这两个函数调用时渲染
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 | |
}; |
正文完
发表至: javascript
2019-05-13