vue 如何生成一个dom元素_vue源码阅读五:虚拟DOM是如何渲染成真实的DOM的?(下)...

前言

上文中讲了如何将普通的虚拟DOM转为真实的DOM,本文中则继续讲如何将组件类型的虚拟DOM转为真实的DOM。

组件类型的Vnode

// 若是组件节点,则调用 createComponent 方法

if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {

return

}

复制代码

如果是组件类型的Vnode,则在生成DOM时,调用的是createComponent方法。

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),这个函数主要的代码如下:

// 将 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的钩子函数又有哪些呢。

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

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中的代码。

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。

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创建好了,在这里也木有立即插入。

当组件内嵌套组件时,在渲染时,遇到组件会再次执行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,然后插入到组件的父元素中。至此,组件的渲染也就讲完了。

总结

dcd762197dec0e12e4960cc9a8181772.png

一图胜千言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值