源码分析 Vue组件注册(全局注册与局部注册)(下)

357 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

上一节我们已经清楚了Vue.component()执行后的结果了,那注册了的组件构造器是在哪儿使用的呢?接下来我们继续分析:

CreateElement

在页面渲染的过程中,会执行updateComponent的方法,这其中会执行vm._render()方法,生成vnode,其中需要触发createElement方法,这方面不懂的可以参考我的另一篇文章# new Vue()的时候发生了什么,以超简单代码为例,后面我会持续更新。

 export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  ......
  let vnode, ns
  // tag 就是传入的component的名字
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    // 不是保留标签
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
      // 组件进入这个逻辑 resolveAsset就是查找当前实例的components上是否存在组件构造器
      } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component  存在组件构造器,当参数传入createComponent中生成组件vnode
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}

resolveAsset就是查找 vm.$options.components[tag],这样我们就可以拿到这个组件对象,用以生成构造函数。

/**
 * Resolve an asset.
 * This function is used because child instances need access
 * to assets defined in its ancestor chain.
 */
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  // 没有id直接返回
  if (typeof id !== 'string') {
    return
  }
  // 获取实例的options.components
  const assets = options[type]
  // check local registration variations first
  // 检查实例中是否存在这个构造器,有则直接返回
  if (hasOwn(assets, id)) return assets[id]
  // 名字转换为驼峰
  const camelizedId = camelize(id)
  // 继续检查实例中是否存在这个构造器,有则直接返回
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  // 名字转换为首字母大写
  const PascalCaseId = capitalize(camelizedId)
  // 继续检查实例中是否存在这个构造器,有则直接返回
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  // 以上都查不到,从原型链查找 
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  // 查找不到报错
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  // 查找到了返回构造器
  return res
}

createComponent

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  // baseCtor 是Vue
  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  // 创建组件构造器,将Vue上面的components也合并了,可以参考我的另一篇文章,extend
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  // 组件构造器不是函数的情况下,报错
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component
  // 异步组件的逻辑,跳过
  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 || {}

  // 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)

  // 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
    }
  }

  // install component management hooks onto the placeholder node
  // 安装钩子函数
  installComponentHooks(data)

  // return a placeholder vnode
  // 创建占位符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
  )

  // Weex specific: invoke recycle-list optimized @render function for
  // extracting cell-slot template.
  // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  /* istanbul ignore if */
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }

  // 返回vnode
  return vnode
}

拿到了这个组件对象后则进入createComponent函数,这个函数使用Vue.extend()函数进行扩展,拥有了Vue上面的components属性,这样就生成组件vnode,方便之后创建真实DOM节点进行挂载操作了。 组件后续如何patch渲染到页面呢?笔者之后会出文章讲述过程。

局部注册

子组件注册的components就会在子组件构造器的options.components中,所以该类型的组件可以在内部访问局部注册的子组件,在内部能拿到局部组件的构造器。之后的逻辑与全局组件类似。

总结

通过Vue.component注册的组件保存于Vue.options.components中,且通过Vue.extend合并到了各个子组件构造器的options上面,所以使用这个组件的时候就能拿到定义的构造器,从而进行之后的逻辑。局部组件与全局组件的不同之处就在于,局部组件只存在与注册了这个组件的构造器的options.components中,只能在注册了这个组件的内部获取,在其他地方访问时是拿不到子组件的构造函数的。