深刻淺出Vue.extend(源碼導讀+實現一個編程式組件)

簡介

Vue.extend做爲一個全局api,固然值得咱們去深刻學習的,同時也是實現編程式組件的重要途徑,因此咱們經過源碼導讀加實踐的方式開始吧。首先咱們會帶着幾個問題來進行學習,若是你都不會,哈哈哈恭喜你,學完本篇你就會明白了。html

  • Vue.extend在Vue當中起到的做用?
  • 講講Vue.extend內部實現?
  • 實現一個編程式組件,說說具體思路?

目錄

  • 基本用法
  • 源碼賞析
  • 源碼導讀
  • 手動實現一個編程式組件
  • 總結

基本用法

參數:

{Object} optionsvue

用法:

使用基礎 Vue 構造器,建立一個「子類」。參數是一個包含組件選項的對象。node

data 選項是特例,須要注意 - 在 Vue.extend() 中它必須是函數。編程

<div id="mount-point"></div>
複製代碼
// 建立構造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 建立 Profile 實例,並掛載到一個元素上。
new Profile().$mount('#mount-point')
複製代碼

結果以下:element-ui

<p>Walter White aka Heisenberg</p>
複製代碼

源碼賞析

在咱們賞析源碼以前,咱們首先確定要找到這個api及調用的位置,這樣才能更好的理解它的做用。經過官方用法咱們就知道,它確定和組件有關係,那麼咱們很容易找到在源碼中建立組件的位置,接下來咱們一步一步找:api

1建立組件:在src/core/vdom/create-element:(113)

vnode = createComponent(Ctor, data, context, children, tag)
複製代碼

2進入createComponent函數:在src/core/vdom/create-component:(115)

if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }
複製代碼

這裏, baseCtor其實就是Vue,具體緣由在之後分析組件源碼會講解,這裏不做研究。咱們終於找到了Vue.extend調用的位置,就是在Vue建立每一個組件的時候調用,經過構造器建立子類。下面就是完整的源碼:緩存

Vue.cid = 0
  let cid = 1
Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    const Sub = function VueComponent (options) {
      this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    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.
    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 cachedCtors[SuperId] = Sub return Sub } } 複製代碼

接下來咱們就一步一步完全搞懂源碼。bash

源碼導讀

a:

extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
複製代碼

首先,extendOptions使咱們傳入進去的模板,這裏面的this就是調用extend的對象,就是Vue,而後保存在變量Super中,變量SuperId就保存Vue中的惟一標識(每一個實例都有本身惟一的cid).app

b:

const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
複製代碼

這一段做爲緩存策略的,放在下面說。dom

c:

const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }
複製代碼

用一個name變量來保存組件的名字,就是我寫組件時候的name,若是沒寫就使用父組件的name,而後對name經過validateComponentName函數驗證,主要就是判斷name不能是html元素和不能是非法命名。

d:

Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
複製代碼

上面咱們建立一個子類Sub,這裏咱們經過繼承,使Sub擁有了Vue的能力,而且添加了惟一id(每一個組件的惟一標識符)

e:

Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super
複製代碼

這裏調用了mergeOptions函數實現了父類選項與子類選項的合併,而且子類的super屬性指向了父類。

f:

if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }
複製代碼

初始化了props和computed.

g:

// 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
    }
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
複製代碼

將父類的方法複製到子類,包括extend,mixin,use,component,directive,filter.還新增屬性superOptions,extendOptions,sealedOptions 。

h:

// cache constructor
    cachedCtors[SuperId] = Sub
複製代碼

以前的代碼結合,將父類的id保存在子類的屬性上,屬性值爲子類,在以前會進行判斷若是構造過子類,就直接將父類保存過的id值給返回了,就是子類自己不須要從新初始化,,做爲一個緩存策略。

整體來講,其實就是建立一個Sub函數並繼承了父級。

手動實現一個編程式組件

咱們在通常調用組件時,都會先組件註冊,再在模板中引用。一個兩個仍是能夠接受的,那麼若是這個組件不少不少呢,因此每次註冊應用就顯得力不從心了。用過element-ui咱們都知道,能夠不用註冊,直接經過命令調用:

this.$message.success('成功了')
複製代碼

是否是特別特別方便,簡單明瞭,並且還符合編程思惟。接下來咱們將手動實現一下:

a:建立一個組件(用於編程式調用的)

<template>
  <div class='toast'
       v-show='isShow'>
    {{message}}
  </div>
</template>
<script>
export default {
  data () {
    return {
      message: '',
      isShow: false
    }
  },
  methods: {
    show (message, duration) {
      this.message = message
      this.isShow = true
      setTimeout(() => {
        this.isShow = false
        this.message = ''
      }, duration)
    }
  }
}
</script>
<style scoped>
.toast {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 999;
  padding: 8px 10px;
  background-color: rgba(0, 0, 0, 0.3);
}
</style>
複製代碼

這個組件很是簡單,兩個變量分別是顯示的消息和是否顯示。我還定義了一個方法,做用就是使模板取消隱藏,而且傳入一個duration參數經過定時器表示顯示的時間,時間一到isShow就變false,能夠說是很是簡單了,模板有了,下面開始重點了。

b:實現編程式

import Toast from './toast'
const obj = {}
obj.install = function (Vue) {
  // 建立構造器
  const ToastContrystor = Vue.extend(Toast)
  // new的方式 根據組件構造器,能夠建立組件對象
  const toast = new ToastContrystor()
  // 手動掛載某一個元素上
  toast.$mount(document.createElement('div'))
  // toast.$el對應的就是div
  document.body.appendChild(toast.$el)
    //組件掛載到Vue原型上
  Vue.prototype.$toast = toast
}
export default obj
複製代碼

把組件看成插件同樣,經過Vue.use()使用,註釋都寫的很是清楚了,一步一步理解。

c:在main.js註冊

import Vue from 'vue'
import toast from '@/components/common/toast/index.js'
Vue.use(toast)
複製代碼

d:使用

this.$toast.error('註冊失敗,請從新輸入', 1000)
複製代碼

就能夠在項目各個地方調用了。咱們就實現了一個編程式的組件。

總結

VUe.extend整體來講其實就是建立一個類來繼承了父級,頂級必定是Vue.這個類就表示一個組件,咱們能夠經過new的方式來建立。學習了extend咱們就很容易的實現一個編程式組件。

相關文章
相關標籤/搜索