组件渲染

main.js

import Vue from 'vue'
import App from './App.vue'
var app = new Vue({
  el: '#app',
  render: h => h(App)
})

App.vue

Hello {{name}}

mountComponentvm.$el = el

一、根节点组件

if (!prevVnode) {
      // initial render
      //调用在平台index.js中安装的_patch_方法
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {

存在旧节点(vm.$el),创建新节点实例并删除旧节点

if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      // 如果是首次patch 创建新节点
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      //存在老节点
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        ...
      } else {
        // 如果老节点是一个真实dom 创建对应节点
        if (isRealElement) {
          // 不是服务端渲染 创建空节点替换旧节点
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        // create new node
        // 为新vnode创建元素/组件实例
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )
        // destroy old node
        // 移除老节点
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }
  • 调用createComponent创建组件节点,不是组件节点则继续执行创建对应dom节点;
  • 根据vnode的数据结构创建真实的dom节点,如果vnode有children则会遍历这些子节点,递归调用createElm方法;
  • insertedVnodeQueue记录子节点创建顺序的队列,每创建一个dom元素就会往队列中插入当前的vnode,当patch完成,整个vnode对象全部转换成为真实的dom 树时,会依次调用这个队列中vnode hook的insert方法;
  • insert方法是在createComponent vnode时安装的子类构造函数。
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm){
      return
    }
    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
      // 创建vnode对应的DOM元素节点vnode.elm
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      // 设置vnode的scope
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        ……
      } else {
        // 调用createChildren遍历子节点创建对应的DOM节点
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          // 执行create钩子函数(directives events等的create hook)
          // updateAttrs
          // updateClass
          // updateDomListeners 普通的原生事件
          // updateDomProps
          // updateStyle
          // updateDirectives
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        // 将DOM元素插入到父元素中
        insert(parentElm, vnode.elm, refElm)
      }
    }
  • 执行创建component VNode时绑定的init钩子函数,创建子组件实例赋值给vnode.componentInstance,并调用$mount触发子组件渲染;
  • 调用initComponent初始化组件,将子组件实例的根元素节点$el(子组件的dom树)赋值给子组件vnode.elm;
  • 将vnode.elm插入到DOM Tree中(当前#app)
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      // 执行init hook生成componentInstance组件实例
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */)
      }
      
      if (isDef(vnode.componentInstance)) {
        // 调用initComponent初始化组件
        initComponent(vnode, insertedVnodeQueue)
        // 将vnode.elm插入到DOM Tree中
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }

创建子组件实例并触发子组件渲染:

init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
      ……
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  }

保存组件父子关系,并调用子组件构造函数,触发子组件_init

  • parent:instance/lifecycleactiveInstance,此时是父组件实例
  • vnode: 当前子组件vnode
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,//当前组件vnode
    parent//当前vue实例
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  //继承于vue的子组件的构造函数
  return new vnode.componentOptions.Ctor(options)
}

创建vnode时createComponent构建子组件构造函数:

  • 构建子类构造函数,获取propsData、listeners
  • 安装子类钩子函数
  • 创建并返回vnode
export function createComponent (
  Ctor: Class | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array,
  tag?: string
): VNode | Array | void {
  if (isUndef(Ctor)) {
    return
  }

  //vue
  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  data = data || {}

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)

  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  const listeners = data.on
  data.on = data.nativeOn

  // install component management hooks onto the placeholder node
  installComponentHooks(data)

  // return a placeholder 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
}

子组件初始化,并调用initInternalComponent初始化子组件状态

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // 设置$option上的propsData _parentListeners
      initInternalComponent(vm, options)
    } else {
      ……
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    //子组件没有el,不执行挂载,子组件挂载在父组件创建子组件实例后,手动调用$mount
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
  • vm.$options.parent 指向父组件实例;
  • vm.$options._parentVnode 指向当前子组件实例的vnode;
  • 从当前子组件vnode(options._parentVnode)中取出父组件传递的listeners、propsData、children、tag保存到当前子组件实例的$options中;
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

执行子组件render函数

Vue.prototype._render = function (): VNode {
    const vm: Component = this
    // _parentVnode 组件节点的外壳节点
    const { render, _parentVnode } = vm.$options
   ……
    vm.$vnode = _parentVnode
    
      currentRenderingInstance = vm
      vnode = render.call(vm._renderProxy, vm.$createElement)
   
      currentRenderingInstance = null
    ……
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }

二、App组件

  • 此时vm.$el为null
  • 处理子组件时,调用setActiveInstance设置当前实例activeInstance为子组件实例,处理完成时重置为父组件实例
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    if (!prevVnode) {
      //调用在平台index.js中安装的_patch_方法
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      ……
    }
    restoreActiveInstance()
    ……
  }

没有旧节点,直接创建新节点,此时没有传入parentElm

return function patch (oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    // 记录被插入的vnode队列,用于批触发insert
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
    ……
    // 调用insert钩子
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
}
  • 先生成组件实例的elm(即组件跟实例节点),因为没有传入parentElm,elm并不执行插入父级,而是子组件全部patch完成后由父组件负责插入;
  • 调用createChildren遍历子节点创建对应的DOM节点;
function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm){
      return
    }

    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
      // 创建vnode对应的DOM元素节点vnode.elm
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      // 设置vnode的scope
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
       
      } else {
        // 调用createChildren遍历子节点创建对应的DOM节点
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          // 执行create钩子函数(directives events等的create hook)
          // updateAttrs
          // updateClass
          // updateDomListeners 普通的原生事件
          // updateDomProps
          // updateStyle
          // updateDirectives
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        // 将DOM元素插入到父元素中
        insert(parentElm, vnode.elm, refElm)
      }
    } else if (isTrue(vnode.isComment)) {
      // 创建注释节点vnode.elm,并插入到父元素中
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      // 创建文本节点vnode.elm,并插入到父元素中
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }

你可能感兴趣的:(组件渲染)