【Vue源码】第十节组件化之认识createComponent

Vue的另一一个核心思想是组件化。组件中的资源是独立的,可以在系统内复用,组件和组件之间也可以相互的嵌套。这次要分析的案例是这个:

import Vue from 'vue'
import App from './App.vue'

var app = new Vue({
    
    
  el: '#app',
  // 这里的 h 是 createElement 方法
  render: h => h(App)
})

组件的创建在create-element.js_createElement函数中:

// src/core/vdom/create-element.js
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
    
    
  // 判断data是否含有__ob__属性, Vue中被j监听的data都会添加上__obj__
  if (isDef(data) && isDef((data: any).__ob__)) {
    
    
    // 抛出警告的代码(省略)
    // 返回空的VNode
    return createEmptyVNode()
  }
  // 判断是否时动态组件(即有没有is属性)
  if (isDef(data) && isDef(data.is)) {
    
    
    tag = data.is
  }
  // 如果tag是false
  if (!tag) {
    
    
    // 返回空的VNode
    return createEmptyVNode()
  }
 
  let vnode, ns
  // 如果tag是string类型
  if (typeof tag === 'string') {
    
    
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    // 判断是不是html的内置标签
    if (config.isReservedTag(tag)) {
    
    
		// ...
    // 判断tag是不是已注册的组件名
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
    
    
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
    
    
      // ...
    }
  } else {
    
    
    // 这次我们传递的是 App, 是Component类型,所以走到了这里
    vnode = createComponent(tag, data, context, children)
  }	
  
  // 最后对vnode又做了一次校验,最后返回vnode
  // ...
}
// src/core/vdom/create-component.js
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | void {
    
    
  if (isUndef(Ctor)) {
    
    
    return
  }

  // 在最开始的_init阶段,initGlobalAPI函数中有这么一段代码: Vue.options._base = Vue
  // 又因为把 Vue 上的一些 option 扩展到了 vm.$options 上
  // vm.$options = mergeOptions(
  //    resolveConstructorOptions(vm.constructor),
  //    options || {},
  //    vm
  //  )
  // 所以这里的 baseCtor 就是Vue
  const baseCtor = context.$options._base
  
  // [1]构造子类构造函数
  // 每一个组件的时候都使用了 export defalut, 即每一个组件都是对象
  if (isObject(Ctor)) {
    
    
    // src/core/global-api/extend.js中查看Vue.extend 函数的定义,分析extend
    // 这样当我们去实例化 Sub 的时候,就会执行 this._init 逻辑再次走到了 Vue 实例的初始化逻辑
    Ctor = baseCtor.extend(Ctor)
  }

  // 首先判断如果Ctor不是函数则抛出警告并结束函数
  if (typeof Ctor !== 'function') {
    
    
    if (process.env.NODE_ENV !== 'production') {
    
    
      warn(`Invalid Component definition: ${
      
      String(Ctor)}`, context)
    }
    return
  }

  // 异步组件相关
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    
    
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
    if (Ctor === undefined) {
    
    
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
  // 对data做初始化处理
  data = data || {
    
    }

  // 用 resolveConstructorOptions 解析构造函数 Ctor 的 options
  resolveConstructorOptions(Ctor)

  // 这一段涉及到了 v-model 指令
  if (isDef(data.model)) {
    
    
    transformModel(Ctor.options, data)
  }

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

  // functional component
  if (isTrue(Ctor.options.functional)) {
    
    
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn

  if (isTrue(Ctor.options.abstract)) {
    
    
    // abstract components do not keep anything
    // other than props & listeners & slot

    // work around flow
    const slot = data.slot
    data = {
    
    }
    if (slot) {
    
    
      data.slot = slot
    }
  }

  // [2]安装组件钩子函数, 分析installComponentHooks
  // 整个 installComponentHooks 的过程就是把 componentVNodeHooks 的钩子函数合并到 data.hook 中
  installComponentHooks(data)

  // [3]通过 new VNode 实例化一个 vnode 并返回。
  // 需要注意的是和普通元素节点的 vnode 不同,组件的 vnode 是没有 children 的
  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
  )
  
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    
    
    return renderRecyclableComponentTemplate(vnode)
  }
  
  return vnode
}

// extend
// src/core/global-api/extend.js
// 作用:构造Vue的子类
Vue.extend = function (extendOptions: Object): Function {
    
    
  extendOptions = extendOptions || {
    
    }
  const Super = this
  const SuperId = Super.cid
  const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {
    
    })
  // 避免多次执行 Vue.extend 的时候对同一个子组件重复构造
  if (cachedCtors[SuperId]) {
    
    
    return cachedCtors[SuperId]
  }
 
  // 函数对 extendOptions 的 name 属性(也就是组件名)进行校验
  const name = extendOptions.name || Super.options.name
  if (process.env.NODE_ENV !== 'production' && name) {
    
    
    validateComponentName(name)
  }
  // 使用一种非常经典的原型继承的方式把一个纯对象转换一个继承于 Vue 的构造器 Sub 并返回
  const Sub = function VueComponent (options) {
    
    
    this._init(options)
  }
  Sub.prototype = Object.create(Super.prototype)
  Sub.prototype.constructor = Sub
  Sub.cid = cid++
  // 扩展 options
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  Sub['super'] = Super

  // For props and computed properties, we define the proxy getters on
  // the Vue instances at extension time, on the extended prototype. This
  // avoids Object.defineProperty calls for each instance created.
  //对配置中的 props 和 computed 做了初始化工作
  if (Sub.options.props) {
    
    
    initProps(Sub)
  }
  if (Sub.options.computed) {
    
    
    initComputed(Sub)
  }

  // allow further extension/mixin/plugin usage
  Sub.extend = Super.extend
  Sub.mixin = Super.mixin
  Sub.use = Super.use

  // create asset registers, so extended classes
  // can have their private assets too.
  ASSET_TYPES.forEach(function (type) {
    
    
    Sub[type] = Super[type]
  })
  // enable recursive self-lookup
  if (name) {
    
    
    Sub.options.components[name] = Sub
  }

  // keep a reference to the super options at extension time.
  // later at instantiation we can check if Super's options have
  // been updated.
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({
    
    }, Sub.options)

  // cache constructor
  // 最后对于这个 Sub 构造函数做了缓存,避免多次执行 Vue.extend 的时候对同一个子组件重复构造
  // 如果这个组件被其他组件多次引用,那么这个组件会多次作为参数传给 extend 函数,
  // 这样检查到之前的缓存就可以直接将 Sub 返回而不用重新构造了
  cachedCtors[SuperId] = Sub
  return Sub
}
// installComponentHooks
// src/core/vdom/create-component.js
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];
    // 在componentVNodeHooks中定义了init、prepatch、insert、destroy4个钩子函数
    const toMerge = componentVNodeHooks[key];
    // 在合并过程中,如果某个时机的钩子已经存在 data.hook 中,那么通过执行 mergeHook 函数做合并
    if (existing !== toMerge && !(existing && existing._merged)) {
    
    
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge;
    }
  }
}

总结

_createElement中,组件转为VNode主要依靠createComponent函数,在createComponent函数中,我们主要经历了一下步骤:

  • 构造子类构造函数, 每次初始化组件的时候,就可以执行组件内部的_init方法;
  • 安装组件钩子函数,即合并钩子函数;
  • 实例化VNode

组件最终也被转换为了VNode,接下来也要通过_update中的patch方法转为真实的DOM。

猜你喜欢

转载自blog.csdn.net/qq_34086980/article/details/105934034