iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 9
1
Modern Web

技術在走,Vue.js 要有系列 第 9

|D9| 從原始碼看 Vue 元件化 (2) - createComponent

以 Vue-cli3 初始化的進入點來說,傳入的 APP 是一個 Component

// min.js

import Vue from "vue";
import App from "./App.vue";

new Vue({  
  // h 是 createElement 方法
  render: h => h(App)
}).$mount("#app");

調用 createComponent 方法來創建 VNode,先看5個參數的定義

  • Ctor: 可以是 Component Class 或 function 等
  • data: 元件的數據
  • context: 當前的 Vue 實例
  • children: 該元件的子節點(VNode)
  • tag: 不一定要帶此參數,若有要是 string 型別
// 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 | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  const baseCtor = context.$options._base

  
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(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)
    if (Ctor === undefined) {
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }

  data = data || {}

 
  resolveConstructorOptions(Ctor)

  
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

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

  /****************************
    判斷元件是否為 functional 元件
  ****************************/
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  /****************************
    元件上事件的處理,提取元件上的事件監聽器,listeners 是作為子元件的監聽器,不是 真實 DOM 的,然後為了讓父元件在 patch 階段可以處理,所以要替換 .native 修飾符的監聽器
  ****************************/
  const listeners = data.on
  data.on = data.nativeOn

  /****************************
    判斷元件是否為抽象元件
  ****************************/
  if (isTrue(Ctor.options.abstract)) {
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

 
  installComponentHooks(data)

  /****************************
    createComponent 最後也是創建 vnode,與 _createElement 創建的 vnode,最大區別在於 componentOptions 上,componentOptions 會保存元件的信息
  ****************************/
  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
}

舉個例子

Vue.component('current-time', {
      data () {
        return {
          time: new Date()
        }
      },
      template: `<span>{{time}}</span>`
})
var app = new Vue({
      el: '#app',
      template: `
      <div class="hello" @click="addCount">
        <span>{{count}}</span>
        <current-time></current-time>
      </div>
      `,
      data: {
        count: 1
      },
      methods: {
        addCount() {
          this.count += 1
        }
      }
})

我們註冊了自定義的 current-time 元件,在 app 實例中, div 裡有一個 DOM 元素跟自定義元件

  • 下圖左是 sapn 原生 html 標籤透過 _createElement 創建的ㄧ個 vnode,沒有 componentOptions
  • 下圖右是 current-time 元件透過 createComponent 創建的ㄧ個 vnode,在 componentOptions 上會保存元件的信息


圖片來源:Vue2.x源码解析系列九


上一篇
|D8| 從原始碼看 Vue 元件化 (1) - 概念介紹
下一篇
|D10| 從原始碼看 Vue 元件化 (3) - patch
系列文
技術在走,Vue.js 要有30

尚未有邦友留言

立即登入留言