Dialog & Message-Box 實現

模擬系統的消息提示框而實現的一套模態對話框組件,用於消息提示、確認消息和提交內容。html

概述:該組件的結構、原理與Toast 組件相似,因此這篇文章會減小組件開發的介紹,而增長一些Vue.extendVue生命週期相關源碼的解讀。vue

主要有如下幾點:
  • Message-Box的基本功能實現;
  • Vue.extend$mount原理以及相應的優化點。

1. 實例

最終效果

代碼git

<!-- 基礎用法 -->
this.$confirm({
    title: "自定義提示",
    content: `<h1 style="color: red;">自定義HTML</h1>`,
    onConfirm: () => this.$message({
        content: "肯定"
    }),
    onCancel: () => this.$message({
        type: "warn",
        content: "取消"
    })
});
this.$alert({
    title: "標題名稱",
    content: "這是一段內容",
    onConfirm: () =>
    this.$message({
        content: "肯定"
    })
});
複製代碼

實例地址:Hover-Tip 實例github

代碼地址:Github UI-Libraryapi

2. 原理

將Message-Box分爲兩部分:緩存

  • Message-Box組件,用於在頁面中顯示模態框,包含確認、取消、關閉三個操做;
  • Vue插件的封裝,利用this.$alert({...})this.$confirm({...})在頁面中掛載 Message-Box 組件。

首先開發Message-Box組件,其基本template以下app

<div class="mock" v-if="visible">
    <div :class="['message-box']">
        <!-- header -->
        <h5 class="message-box-header c-size-l">
            <span>{{ title }}</span>
            <fat-icon v-if="showClose" name="close" class="close-btn" @click.stop="close" />
        </h5>
        <!-- content -->
        <div class="message-box-content c-size-m" v-html="content" >
        </div>
        <!-- footer -->
        <div class="message-box-footer">
            <fat-button size="mini" v-if="cancelButtonText && type !== 'alert'" @click.stop="handleClick('cancel')" >{{ cancelButtonText }}</fat-button>
            <fat-button size="mini" type="success" v-if="confirmButtonText" @click.stop="handleClick('confirm')" >{{ confirmButtonText }}</fat-button>
        </div>
    </div>
</div>
複製代碼

基本結構很是簡單,清晰的三段:函數

  • 最上層的visible狀態用於控制整個模態框的顯示、消失,header部分,包含title,以及關閉按鍵;
  • 中間content部分,包含傳入的content,爲了支持HTML,因此採用v-html
  • 最底層footer部分,包含兩個按鈕,若是當前模態框的類型是alert則只有confirm,若是爲confirm,則須要添加cancel

所涉及的datamethods以下post

export default {
    data() {
        return {
            // 控制模態框的顯示
            visible: true
        }
    },
    watch: {
        visible(newValue) {
            if (!newValue) {
                // 過渡結束後註銷組件
                this.$el.addEventListener('transitionend', this.destroyElement)
            }
        }
    },
    mounted() {
        document.body.appendChild(this.$el)
    },
    destroyed() {
        this.$el.parentNode.removeChild(this.$el)
    },
    methods: {
        destroyElement() {
            this.$destroy()
        },
        close() {
            // 關閉模態框
            this.visible = false
        },
        handleClick(type) {
            // 處理對應的點擊事件
            this.$emit(type);
            this.close();
        }
    }
}
複製代碼

能夠看到在該組件的mounteddestroyed兩個生命週期中完成組件在頁面中的掛載與註銷優化

mounted() {
    document.body.appendChild(this.$el)
},
destroyed() {
    this.$el.parentNode.removeChild(this.$el)
}
複製代碼

其中註銷的順序是:

  • 首先使組件的visible狀態爲false,使它在頁面中消失,而後觸發模態框transition的過分動畫;
  • 以後監聽addEventListener('transitionend', this.destroyElement),若是過渡結束,就觸發對應的destroyElement觸發組件的生命週期destroyed,在組件中註銷this.$el.parentNode.removeChild(this.$el)

相對於註銷,掛載則要複雜的一些,因爲這部分涉及到了封裝,因此一塊兒梳理。

// 引入上述Message-Box組件
import messageBox from './messagebox.vue'
// 生成Message-Box對應的構造器
const Constructor = Vue.extend(messageBox)

function generateInstance(options, type = 'alert') {
    let instance = new Constructor({
        propsData: Object.assign(options, {
            type
        }),
    }).$mount(document.createElement('div'))
    ...
    return instance
}
複製代碼

首先import模態框組件,而後利用Vue.extend建立一個Message-Box組件的構造器。

當調用this.$alert以及this.$confirm時利用new Constructor建立一個Message-Box組件,這時顯式調用vm.$mount()手動開啓編譯,此時會觸發組件的mounted生命週期

mounted() {
    document.body.appendChild(this.$el)
}
複製代碼

完成在頁面中的掛載。

最後一步,利用Vue.use完成封裝,在Vue.prototype上添加$alert以及$confirm方法。

export default {
    install(Vue) {
        Vue.prototype.$alert = (options = {}) => generateInstance(options)
        Vue.prototype.$confirm = (options = {}) => generateInstance(options, 'confirm')
    }
}
複製代碼

3. 源碼

闡述下 Vue.extend 的源碼,在 src/core/global-api/extend.js 中,比較關鍵的點在於:

  • 如何經過已有組件 object 生成對應構造器 constructor
  • 對於同一個組件的 constructor ,是否存在什麼優化。
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 Sub = function VueComponent (options) {
        this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    ...
    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
}
複製代碼

從源碼中能夠看出,Vue.extend 是一個變式的原型式繼承

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
複製代碼

臨時的構造函數 function Ffunction VueComponent,函數中 this._init 指向的是初始化Vue時候的 _init function

最終效果

在將 F.prototype 指向 Object.create(Super.prototype) ,這樣能夠繼承 Vue 自己原型上的一些方法,最後 return constructor

以後 Vue 還作了緩存處理,因此屢次利用 Vue.extend 建立Message-Box、Toast、Message時,並不會影響效率/

if (cachedCtors[SuperId]) {
    return cachedCtors[SuperId]
}

// cache constructor
cachedCtors[SuperId] = Sub
複製代碼
  1. 總結

深究了一下 Vue.extend 背後的原理,以及如何用它來是實現組件。

參考文章:

原創聲明: 該文章爲原創文章,轉載請註明出處。

相關文章
相關標籤/搜索