@component 构造函数_细谈 vue - component 篇

本篇文章是细谈 vue 系列的第六篇。看过我这个系列文章的小伙伴都知道:文章贼长,看不下去的建议先点个赞,有时间静下来慢慢看,前端交流群:731175396。以前的文章传送门如下

  • 《细谈 vue 核心 - vdom 篇》
  • 《细谈 vue - slot 篇》
  • 《细谈 vue - transition 篇》
  • 《细谈 vue - transition-group 篇》
  • 《细谈 vue - 抽象组件实战篇》

用过 vue 的小伙伴肯定知道,在 vue 的开发中,component 可谓是随处可见,项目中的那一个个 .vue (SFC) 文件,可不就是一个个的组件么。

那么,既然 component 这么核心,这么重要,为何不好好来研究一波呢?

why not ?
​ — 鲁迅

一、组件创建

之前我们分析 vdom 的时候分析过一个函数 createElement,与它相同的是 createComponent,两者都是用来创建 vnode 节点的,如果是普通的 html 标签,则直接实例化一个普通的 vnode 节点,否则通过 createComponent 来创建一个 Component 类型的 vnode 节点

1、createElement

这里仅列出不同情况下 vnode 节点创建的代码

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
    )
  } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
    
    // component
    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)
}

2、createComponent

接下来,我们先看 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
  }

  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  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)
    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
  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)
  }

  return vnode
}
  • 在其内部,第一件事情就是将构造函数 Vue 赋值给变量 baseCtor ,并通过 extend 将参数 Ctor 进行扩展
const baseCtor = context.$options._base
if (isObject(Ctor)) {
    
  Ctor = baseCtor.extend(Ctor)
}

这里我们看到 $options._base ,其实就是构造函数 Vue

// src/core/global-api/index.js
Vue.options._base = Vue

// src/core/instance/init.js
// 1. initMixin()
if (options && options._isComponent) {
    
  initInternalComponent(vm, options)
} else {
    
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
}
// 2. initInternalComponent()
const opts = vm.$options = Object.create(vm.constructor.options)
  • 其次,紧接着,判定组件是否为异步组件、函数式组件或者抽象组件。具体每种情况的处理后面我再详细分析
// 异步组件
let asyncFactory
if (isUndef(Ctor.cid)) {
    
  asyncFactory = Ctor
  Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
  if (Ctor === undefined) {
    
    return createAsyncPlaceholder(
      asyncFactory,
      data,
      context,
      children,
      tag
    )
  }
}

// 函数式组件
if (isTrue(Ctor.options.functional)) {
    
  return createFunctionalComponent(Ctor, propsData, data, context, children)
}

// 抽象组件
if (isTrue(Ctor.options.abstract)) {
    
  const slot = data.slot
  data = {}
  if (slot) {
    
    data.slot = slot
  }
}
  • 对于组件上的事件也有相关处理,它会提取组件上的事件监听器。它需要作为子组件的监听器,而并非DOM监听器。所以需要将其替换为拥有 .native 修饰符的侦听器,让其能在父组件 patch 阶段能够得到处理
const listeners = data.on
data.on = data.nativeOn
  • 然后,安装组件的钩子函数。它将 componentVNodeHooks 的钩子函数合并到 data.hook 中,然后 Component 类型的 vnode 节点在 patch 过程中会执行相关的钩子函数,如果某个时机的钩子函数已经存在,则通过 mergeHook 将函数合并,即依次执行同一时机的这两个函数
installComponentHooks(data)

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

const hooksToMerge = Object.keys(componentVNodeHooks)

function mergeHook (f1: any, f2: any): Function {
    
  const merged = (a, b) => {
    
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}

3、componentVNodeHooks

上面的 componentVNodeHooks 则是组件初始化的时候实现的几个钩子函数,分别有 initprepatchinsertdestroy

const componentVNodeHooks = {
    
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
    
      const mountedNode: any = vnode
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
    
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentV
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值