全局掛載組件之Vue.extend

Vue.extend 屬於Vue的全局 api,在實際業務開發中咱們不多使用,由於相比經常使用的 Vue.component 寫法使用 extend 步驟要更加繁瑣一些。可是在一些獨立組件開發場景中(例如:ElementUI庫),因此Vue.extend + $mount 這對組合很是有必要須要咱們瞭解下。javascript

Vue.component

文檔html

官網用法vue

註冊或獲取全局組件。註冊還會自動使用給定的 id 設置組件的名稱java

// 註冊組件,傳入一個擴展過的構造器
Vue.component('my-component', Vue.extend({ /* ... */ }))

// 註冊組件,傳入一個選項對象 (自動調用 Vue.extend)
Vue.component('my-component', { /* ... */ })

// 獲取註冊的組件 (始終返回構造器)
var MyComponent = Vue.component('my-component')
let ElInput = Vue.component('ElInput');
console.log(new ElInput);  // 就是Inout的實例
複製代碼

用法也特別的簡單,你寫好的組件,直接在main.js裏面導入而後使用Vue.component('xx-xxx',xxx)就能夠全局通用了。node

  • 好處
    • 這樣的好處就是全部頁面基本上都是經過 router 來管理能夠直接註冊,組件的建立咱們不須要去關注,相比 extend 要更省心一點
  • 缺點
    • 組件的名稱都是自定義的,若是我要從接口動態渲染怎麼辦。【extend不用必須在初始化的時候完成,下面有實例】
    • 有內容都是在 #app 下渲染,註冊組件都是在當前位置渲染。若是我要實現一個模態框的提示組件,就比較雞肋了。

這時候,Vue.extend + vm.$mount 組合就派上用場了。web

Vue.extend

文檔api

官網用法:瀏覽器

<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')
複製代碼

結果以下:緩存

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

能夠發現Idmount-point的div ,是直接被替代了,沒有在內部填充,在咱們 main.js 初始化的時候用發其實也是同樣:app

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app');  // 此處都是替換,不是填充
複製代碼

下面簡單的分析下$mount的源碼

// src/platforms/web/entry-runtime-with-compiler.js
import config from 'core/config'
import {warn, cached} from 'core/util/index'
import {mark, measure} from 'core/util/perf'

import Vue from './runtime/index'
import {query} from './util/index'
import {compileToFunctions} from './compiler/index'
import {shouldDecodeNewlines, shouldDecodeNewlinesForHref} from './util/compat'

const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

const mount = Vue.prototype.$mount;  // 緩存了原型上的 $mount 方法
Vue.prototype.$mount = function (el, hydrating) {
  el = el && query(el)  // 經過 query函數 就是獲取真實的DOM元素

  // 判斷獲取的真實 dom元素是否爲 body 或 documentElement 報警告
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // 判斷有麼有render , 我們通常都是用 template 寫的vue,須要編譯
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)  // 即獲取 el 的 outerHTML
    }
    if (template) {
      // 而後經過compileToFunctions將 template 轉化爲 render 函數,options.render = render
      // 全部 Vue 的組件的渲染最終都須要 render 方法,不管是用單頁面 .vue 方式開發,仍是寫了 el 或者 template 屬性,
      // 最終都要被轉成 render 方法,那麼這個過程是 Vue 的一個 「在線編譯」 的過程, 它是調用 compileToFunctions 方法實現的,最後調用原型上的 $mount 方法掛載。
      const {render, staticRenderFns} = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  // 若是沒有傳el的話,會直接執行這步,會把組件在內存中渲染完畢
  return mount.call(this, el, hydrating)
}
// 及時獲取HTML 兼容IE
function getOuterHTML(el): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}
Vue.compile = compileToFunctions

export default Vue
複製代碼
  • $mount方法支持傳入 2 個參數,第一個是 el,它表示掛載的元素,能夠是字符串,也能夠是 DOM 對象,若是是字符串在瀏覽器環境下會調用 query 方法轉換成 DOM 對象的。第二個參數是和服務端渲染相關,在瀏覽器環境下不須要傳第二個參數。

  • $mount方法實際上會調用mountComponent 方法,方法定義在 src/core/instance/lifecycle.js

實現一個彈框組件

// message/src/index.vue
<template>
    <div class="wrap">
        <div class="message" :class="item.type" v-for="item in notices" :key="item._name">
            <div class="content">{{item.content}}</div>
        </div>
    </div>
</template>

<script>
  // 默認選項
  const defaultOptions = {
    duration: 1500,
    type: 'info',
    content: '這是一條提示信息!',
  }
  let mid = 0
  export default {
    name:'MyMessage', // 建議添加方便外面直接取值
    data() {
      return {
        notices: []
      }
    },
    methods: {
      addMessage(notice = {}) {
        // name標識 用於移除彈窗
        let _name = this.getName()
        // 合併選項
        notice = Object.assign({
          _name
        }, defaultOptions, notice)

        this.notices.push(notice)

        setTimeout(() => {
          this.removeNotice(_name)
        }, notice.duration) 
      },
      getName() {
        return 'msg_' + (mid++)  //建立一個惟一的值 
      },
      removeNotice(_name) {
        let index = this.notices.findIndex(item => item._name === _name)
        this.notices.splice(index, 1)  // 刪除當前超時的dom
      }
    }
  }
</script>

<style scoped>
    .wrap {
        position: fixed;
        top: 50px;
        left: 50%;
        display: flex;
        flex-direction: column;
        align-items: center;
        transform: translateX(-50%);
    }
    .message {
        --borderWidth: 3px;
        min-width: 240px;
        max-width: 500px;
        margin-bottom: 10px;
        border-radius: 3px;
        box-shadow: 0 0 8px #ddd;
        overflow: hidden;
    }
    .content {
        padding: 8px;
        line-height: 1.3;
    }

    // 對應的集中狀態 
    .message.info {
        border-left: var(--borderWidth) solid #909399;
        background: #F4F4F5;
    }
    .message.success {
        border-left: var(--borderWidth) solid #67C23A;
        background: #F0F9EB;
    }
    .message.error {
        border-left: var(--borderWidth) solid #F56C6C;
        background: #FEF0F0;
    }
    .message.warning {
        border-left: var(--borderWidth) solid #E6A23C;
        background: #FDF6EC;
    }
</style>
複製代碼
// message/index.js
import Vue from 'vue'
import Message from './src/index.vue'

let messageInstance = null
let TempMessage = ''

// 模擬異步請求
setTimeout(() => {
  TempMessage = Message
}, 2000)

let init = () => {
  let MessageConstructor = TempMessage && Vue.extend(TempMessage)

  messageInstance = new MessageConstructor({})// 構造函數能夠接傳值,data、methods.....
  // console.log(messageInstance);

  // $mount()不帶參數,會把組件在內存中渲染完畢
  messageInstance.$mount()

  // messageInstance.$el 拿到的就是組件對應的dom元素,能夠直接操做dom
  document.body.appendChild(messageInstance.$el)
  messageInstance.$el.style.zIndex = 9999
}


let caller = (options) => {
  if (!messageInstance) {
    // 進頁面初始化
    init(options)
  }
  // addMessage 是組件內部聲明的方法,也能夠經過構造函數傳對應的方法
  messageInstance.addMessage(options)
}


export default {
  install(vue) {
    vue.prototype.$mymessage = caller
  }
}
複製代碼
// main.js
import Message from '@/components/Message/index.js'
Vue.use(Message)
複製代碼

使用

this.$mymessage({
   type: 'warning',
   content: '你好壞啊,我好喜歡',
   duration: 6000
 })
複製代碼

參考文章

JavaScript 方式調用的組件

相關文章
相關標籤/搜索