vue源码分析(八)实例分析组件的patch过程

一.组件渲染

从前面的文章分析可以知道Vue.prototype._render函数生成的vnode有两种类型,这是根据传入的options.tag来判断。

  • 普通vnode : 也就是typeof tag===string
  • 组件vnodeoptions中的tag为对象
    _update方法中传入生成的vnode,进而调用__patch__ 方法,在__patch__方法中,又会调用createElm方法将vnode对应的真实dom也就是vnode.elm插入到父节点parentElm上。

createElm方法进行插入的过程中会首先调用createComponent方法,组件vnode执行该方法,并返回true,不执行后续逻辑。

二.实例分析

// index.js
import SubComp from '@components/SubComp.vue'
new Vue({
    el: '#app',
    render(h){
        return h(SubComp)
    }
})

// subComp.vue
<template>
    <div>
        {{text}}
    </div>
</template>

<script >
export default {
    name: 'subComp',
    data() {
        return {
            text: 'subComp'
        }
    }
   
}
</script>

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
       
    </div>
</body>
<script src="./dist/app.bundle.js"></script>
</html>

SubComp会被vue-loader编译成tag为一个对象,所以第一次调用render方法生成的vnode为一个组件vnode.

在当前new Vue( )生成的实例的_update方法中会将生成的组件vnode作为参数进行调用,执行__patch__方法,__patch__ 方法中又会执行createElm方法,这和普通vnode的渲染逻辑一样.不同的是createElm方法。

createElm方法中会首先尝试执行createComponent(vnode),如果结果为false,就继续执行当前方法createElm中的后续逻辑,但我们的实例中返回的结果为true,所以后续的方法不会执行。

下面我们来看看createComponent方法

三. createComponent


 function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */)
      }
      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.
      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue)
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }

记住一点,此时的vnode还是首次调用render方法生成的vnodecreateComponent方法主要做了以下几件事

  • 根据组件vnode生成组件实例
  • initComponent方法中进行赋值,vnode.elm = vnode.componentInstance.$el 这里的$el,是组件实例渲染而成的真实dom
  • 插入insert(parentElm, vnode.elm, refElm),这里的parentElm也就是body

经过上面的三步,组件vnode被渲染成真实的dom并插入到body中,但在此之前,vue还做了很多其他的工作。

我们知道在生成组件vnode时会添加相应的钩子函数,在src/core/vdom/create-component.jscreateComponent方法中执行了installComponentHooks 方法,这个方法是给组件vnodedata中添加相应的钩子函数,因此上面代码中的data.hook.init也就是下面的方法


 init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

init方法中会调用createComponentInstanceForVnode方法,会根据vnode生成组件实例(前面的文章已经分析如何生成组件实例,这里不作赘述),并赋值给当前vnodecomponentInstance属性.然后手动调用$mount方法

注意这里的vnode还是首次_render 函数生成的组件vnode
调用$mount方法又会进入组件实例(继承自Vue)的mountComponent方法,然后调用组件实例child__update,然后__patch__,然后createElm,然后insert,在组件实例对应的insert方法执行完成后,组件SubComp实例的$el 属性是真实的dom,然后赋值给vnode.elm,也就是vnode.elm = vnode.componentInstance.$el,最后执行insert(parentElm, vnode.elm, refElm)

经过上面的分析,我们需要了解以下两点

  • 每个组件都会生成一个组件实例,组件实例具有Vue的所有属性和方法,因此在手动执行$mount方法后,会执行mountComponent方法,执行_render,执行_update,执行__patch__,执行createElm,执行insert方法,一系列方法执行后,最终完成了子组件的vnode渲染成真实的dom($el)过程。子组件SubComp的流程跑完后会继续执行后续逻辑insert(parentElm, vnode.elm, refElm)
  • 无论时普通vnode还是组件vnode都是先子后父插入,当前节点的子节点插入到父节点完成后,才会继续当前节点的插入,以确保节点的父子关系。

查看demo代码,请根据当前文章对demo源码中的index.js或者webpack.config.js作相应改动.

四.流程图

vue源码分析(八)实例分析组件的patch过程_第1张图片

你可能感兴趣的:(vue源码笔记)