We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
上一篇:vue源码阅读四:虚拟DOM是如何渲染成真实的DOM的?(上)
上文中讲了如何将普通的虚拟DOM转为真实的DOM,本文中则继续讲如何将组件类型的虚拟DOM转为真实的DOM。
虚拟DOM
真实的DOM
Vnode
// 若是组件节点,则调用 createComponent 方法 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return }
如果是组件类型的Vnode,则在生成DOM时,调用的是createComponent方法。
DOM
createComponent
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if (isDef(i)) { // 当 vnode 上有 hook 和 init 时,将 i = vnode.data.init if (isDef(i = i.hook) && isDef(i = i.init)) { // 相当于 init(vnode, false) i(vnode, false /* hydrating */) } if (isDef(vnode.componentInstance)) { // 先放在这 initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) return true } } }
我们可以看到,先是判断 vnode.data 上是否有 hook 和 init,如果有的话,则执行init方法 。 而hook和init 是什么时候挂载到vnode.data 上的呢。 在生成组件类型的虚拟DOM的 createComponent 方法中,有这样一个函数installComponentHooks(data),这个函数主要的代码如下:
vnode.data
hook 和 init
init
hook
installComponentHooks(data)
// 将 data.hook 与 componentVNodeHooks 的钩子进行合并 function installComponentHooks(data: VNodeData) { const hooks = data.hook || (data.hook = {}) for (let i = 0; i < hooksToMerge.length; i++) { const key = hooksToMerge[i] const existing = hooks[key] const toMerge = componentVNodeHooks[key] if (existing !== toMerge && !(existing && existing._merged)) { hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge } } }
所以installComponentHooks函数的主要作用是将data.hook与componentVNodeHooks的钩子函数进行合并。而componentVNodeHooks的钩子函数又有哪些呢。
installComponentHooks
data.hook
componentVNodeHooks
const componentVNodeHooks = { init(vnode: VNodeWithData, hydrating: boolean): ?boolean { ... // 创建组件的实例 const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) }, prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { ... }, insert(vnode: MountedComponentVNode) { ... }, destroy(vnode: MountedComponentVNode) { ... } }
componentVNodeHooks 里面有四个钩子,我们先主要看看 init 这个钩子 ,后面三个用到时候,我们再去详细的看。经过钩子的合并,vnode.data 上就有 hook 和 init了。 在 init 这个钩子内,调用 createComponentInstanceForVnode方法创建vue 实例,并将结果赋值给 child 和 vnode.componentInstance。最后调用 child.$mount来渲染组件。详细看下createComponentInstanceForVnode。
createComponentInstanceForVnode
vue
child
vnode.componentInstance
child.$mount
export function createComponentInstanceForVnode( vnode: any, // we know it's MountedComponentVNode but flow doesn't parent: any, // activeInstance in lifecycle state ): Component { const options: InternalComponentOptions = { _isComponent: true, // 组件的标志 _parentVnode: vnode, parent } ... // 创建一个新的 vue 的实例 return new vnode.componentOptions.Ctor(options) } -------------------------------------------------------------------- // tips:在该系列第三篇中,我们介绍了如何生成组件类型的虚拟DOM,其中有如下代码: // componentOptions 中的 Ctor 则是 Vue 的子类,拥有 Vue 的完整的功能 const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, // 对应tag data, // 父组件自定义事件和patch时用到的方法 undefined, // children undefined, // text undefined, // 节点 context, // 当前实例 { Ctor, propsData, listeners, tag, children }, // 对应componentOptions属性 asyncFactory )
在createComponentInstanceForVnode方法的最后,可以看到,调用new vnode.componentOptions.Ctor(options)生成新的vue实例,相当于执行 new Vue() ,接着又会执行最开始的 _init 方法。回顾下_init中的代码。
new vnode.componentOptions.Ctor(options)
new Vue()
_init
Vue.prototype._init = function (options?: Object) { // ... if (options && options._isComponent) { // 优化合并组件内部选项 initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } // 组件没有 el,不会执行 vm.$mount。所以在 componentVNodeHooks 的 init 中 // 使用 child.$mount 来进行组件虚拟 DOM 的构建和渲染 if (vm.$options.el) { vm.$mount(vm.$options.el) } }
再次执行_init方法时,首先使用initInternalComponent优化合并组件内部选项。然后由于没有 vm.$options.el属性,所以没有使用这里的挂载,而是在 componentVNodeHooks 的 init 中使用 child.$mount 来进行组件虚拟 DOM 的构建和渲染。之后就是执行组件的_render 方法得到组件内部元素的虚拟 DOM,接着是_update方法渲染虚拟 DOM。
initInternalComponent
vm.$options.el
虚拟 DOM
_render
_update
export function createPatchFunction(backend) { ... return function patch(oldVnode, vnode, hydrating, removeOnly) { if (isUndef(oldVnode)) { createElm(vnode, insertedVnodeQueue) } } ... }
在渲染的过程中,由于child.$mount(undefined)里传的是undefined,所以在createPatchFunction方法中,oldVnode 是undefined的。createEle 方法中第三个参数parentElm就么得了。所以组件内的真实DOM创建好了,在这里也木有立即插入。
child.$mount(undefined)
undefined
createPatchFunction
oldVnode
createEle
parentElm
当组件内嵌套组件时,在渲染时,遇到组件会再次执行init(),整个过程是递归执行的。当全部的init()执行完后,后面的代码如下:
init()
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if (isDef(i)) { // 当 vnode 上有 hook 和 init 时,将 i = vnode.data.init if (isDef(i = i.hook) && isDef(i = i.init)) { // 相当于 init(vnode, false) i(vnode, false /* hydrating */) } if (isDef(vnode.componentInstance)) { // 将组件内真实 DOM 赋值给 vnode.elm initComponent(vnode, insertedVnodeQueue) // 插入组件内真实的 DOM insert(parentElm, vnode.elm, refElm) return true } } } ------------------------------------------------------------ function initComponent(vnode, insertedVnodeQueue) { // 将组组件内元素的只是 DOM 赋值给 vnode.elm vnode.elm = vnode.componentInstance.$el ... }
这部分代码主要作用是将组件内部元素的真实DOM赋值给 vnode.elm,然后插入到组件的父元素中。至此,组件的渲染也就讲完了。
vnode.elm
一图胜千言。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
上文中讲了如何将普通的
虚拟DOM
转为真实的DOM
,本文中则继续讲如何将组件类型的虚拟DOM
转为真实的DOM
。组件类型的
Vnode
如果是组件类型的
Vnode
,则在生成DOM
时,调用的是createComponent
方法。createComponent
我们可以看到,先是判断
vnode.data
上是否有hook 和 init
,如果有的话,则执行init
方法 。而
hook
和init
是什么时候挂载到vnode.data
上的呢。在生成组件类型的虚拟
DOM
的createComponent
方法中,有这样一个函数installComponentHooks(data)
,这个函数主要的代码如下:所以
installComponentHooks
函数的主要作用是将data.hook
与componentVNodeHooks
的钩子函数进行合并。而componentVNodeHooks
的钩子函数又有哪些呢。componentVNodeHooks
componentVNodeHooks
里面有四个钩子,我们先主要看看init
这个钩子 ,后面三个用到时候,我们再去详细的看。经过钩子的合并,vnode.data
上就有hook 和 init
了。在
init
这个钩子内,调用createComponentInstanceForVnode
方法创建vue
实例,并将结果赋值给child
和vnode.componentInstance
。最后调用child.$mount
来渲染组件。详细看下createComponentInstanceForVnode
。createComponentInstanceForVnode
在
createComponentInstanceForVnode
方法的最后,可以看到,调用new vnode.componentOptions.Ctor(options)
生成新的vue
实例,相当于执行new Vue()
,接着又会执行最开始的_init
方法。回顾下_init
中的代码。再次执行
_init
方法时,首先使用initInternalComponent
优化合并组件内部选项。然后由于没有vm.$options.el
属性,所以没有使用这里的挂载,而是在componentVNodeHooks
的init
中使用child.$mount
来进行组件虚拟 DOM
的构建和渲染。之后就是执行组件的_render
方法得到组件内部元素的虚拟 DOM
,接着是_update
方法渲染虚拟 DOM
。在渲染的过程中,由于
child.$mount(undefined)
里传的是undefined
,所以在createPatchFunction
方法中,oldVnode
是undefined
的。createEle
方法中第三个参数parentElm
就么得了。所以组件内的真实DOM
创建好了,在这里也木有立即插入。当组件内嵌套组件时,在渲染时,遇到组件会再次执行
init()
,整个过程是递归执行的。当全部的init()
执行完后,后面的代码如下:这部分代码主要作用是将组件内部元素的真实
DOM
赋值给vnode.elm
,然后插入到组件的父元素中。至此,组件的渲染也就讲完了。总结
一图胜千言。
The text was updated successfully, but these errors were encountered: