Vue源码阅读(七):组件化机制的实现

--本文采自本人公众号《猴哥别瞎说》vue

MVVM框架中,组件化是一种必要性的存在。node

经过组件化,将页面作切割,并将其对应的逻辑作必定的抽象,封装成独立的模块。react

那么Vue中的组件化是怎样作的呢?咱们分几个点来具体阐述这个过程:组件声明过程Vue.component()的具体实现,组件的建立与挂载过程。bash

组件声明

Vue.component()的声明是一个全局API,咱们在/core/index.js中查看函数initGlobalAPI()的代码:框架

import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'

export function initGlobalAPI (Vue: GlobalAPI) {
  //...省略
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}
复制代码

发现Vue.component()的声明没有明确写在代码里,其声明的过程是动态的。具体过程在initAssetRegisters()中:dom

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {

  // ASSET_TYPES : ['component','directive','filter']
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        //component的特殊处理
        if (type === 'component' && isPlainObject(definition)) {
          //指定name
          definition.name = definition.name || id
          //转换组件配置对象为构造函数
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        //全局注册:options['components'][id] = Ctor
        //此处注册以后,就能够在全局其余地方使用
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}
复制代码

以一个例子来讲明代码的过程吧:async

// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
复制代码

组件的声明过程是:先获取组建的名称,而后将{data:"...", template: "...}转变为构造函数,最后,将该组件的构造函数注册到 this.options.components中。函数

当其余组件使用到该组件的时候,首先会在this.options.components中查询是否存在该组件,由此实现了全局注册。组件化

##自定义组件的建立与挂载 那么,咱们想问:这个自定义组件,是在何时建立的?ui

render过程

首先建立的是根组件,首次_render()时,会获得整棵树的VNode结构,其中必然包括了子组件的建立过程。那么子组件的建立会是在哪一步呢?让咱们来回顾根组件的建立过程:

new Vue() => $mount() => vm._render() => _createElement() => createComponent()
复制代码

_render()的过程当中,会触发_createElement()。在该函数内部,会对是不是自定义组件进行查询,若是是自定义组件,那么会触发createComponent(),其过程为:

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  ...//省略
  // 核心:vnode的生成过程
  // 传入tag多是原生的HTML标签,也多是用户自定义标签
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    // 是原生保留标签,直接建立VNode
    if (config.isReservedTag(tag)) {
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    }else if((!data||!data.pre)&&isDef(Ctor=resolveAsset(context.$options, 'components',tag))) {
      // 查看this.options.components中是否存在对应的tag的构造函数声明
      // 若存在,须要先建立组件,再建立VNode
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      //
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  ...//省略
}
复制代码

那么咱们来看createComponent(),它的主要工做是根据构造函数Ctor获取到VNode:

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
 
  // 省略...
 
  // 安装组件的管理钩子到该节点上
  // 这些管理钩子,会在根组件首次patch的时候调用
  installComponentHooks(data)

  // 返回VNode
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  // 省略...
  return vnode
}
复制代码

installComponentHooks()具体作了什么事情呢?所谓的组件的管理钩子,究竟是些啥东西啊?咱们来具体看看:

const componentVNodeHooks = {
  // 初始化钩子:建立组件实例、执行挂载
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      //建立自定义组件VueComponent实例,并和对应VNode相互关联
      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) {
   ...
  }
}

const hooksToMerge = Object.keys(componentVNodeHooks)

//将自定义组件相关的hooks都放在生成的这个VNode的data.hook中
//在未来的根组件首次patch过程当中,自定义组件经过这些hooks完成建立,并当即挂载
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
    }
  }
}
复制代码

看代码可知,管理钩子一共有四个:init(),prepatch(),insert(),destroy()。看init()的过程:建立自定义组件VueComponent实例,并和对应VNode相互关联,而后马上执行$mount()方法。

update过程

installComponentHooks()过程当中,其实只是将这四个管理钩子放在生成的这个VNode的data.hook中存放。对应的管理钩子调用,在首次执行update()时候,执行patch()的过程里。详细的过程在/core/instance/vdom/patch.jscreateElm()中:

function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    //省略...
    
    //子组件的生成过程
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }
    
    //原生标签的生成过程
    //省略...
  }
 
  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    //获取data
    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.data.hook.init()方法,在此处建立自定义组件实例,并完成挂载工做
        //详细见'./patch.js componentVNodeHooks()'
        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
      }
    }
  }
复制代码

能够看到,自定义组件的建立过程在patch.jscreateComponent()方法中。经过调用上面提到的vnode.data.hook.init()方法(和文章的上一段联系起来),将自定义组件在此处建立,而且当即调用$mount()

这个时候就能够理解如下结论:

组件建立顺序自上而下

组件挂载顺序自下而上

相关文章
相关标签/搜索