模擬系統的訊息提示框而實現的一套模態對話方塊元件,用於訊息提示、確認訊息和提交內容。
概述:該元件的結構、原理與Toast 元件類似,所以這篇文章會減少元件開發的介紹,而增加一些Vue.extend
、Vue
生命週期相關原始碼的解讀。
- Message-Box的基本功能實現;
Vue.extend
、$mount
原理以及相應的優化點。
1. 例項
程式碼
<!-- 基礎用法 -->
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 UI-Library
2. 原理
將Message-Box分為兩部分:
- Message-Box元件,用於在頁面中顯示模態框,包含確認、取消、關閉三個操作;
- Vue外掛的封裝,利用
this.$alert({...})
或this.$confirm({...})
在頁面中掛載 Message-Box 元件。
首先開發Message-Box元件,其基本template
如下
<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
。
所涉及的data
、methods
如下
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();
}
}
}
複製程式碼
可以看到在該元件的mounted
、destroyed
兩個生命週期中完成元件在頁面中的掛載與登出
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 F
為 function 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
複製程式碼
- 總結
深究了一下 Vue.extend
背後的原理,以及如何用它來是實現元件。
參考文章:
往期文章:
- 從零實現Vue的元件庫(零)- 基本結構以及構建工具
- 從零實現Vue的元件庫(一)- Toast 實現
- 從零實現Vue的元件庫(二)- Slider 實現
- 從零實現Vue的元件庫(三)- Tabs 實現
- 從零實現Vue的元件庫(四)- File-Reader 實現
- 從零實現Vue的元件庫(五)- Breadcrumb 實現
- 從零實現Vue的元件庫(六)- Hover-Tip 實現
- 從零實現Vue的元件庫(七)- Message-Box 實現
- 從零實現Vue的元件庫(八)- Input 實現
- 從零實現Vue的元件庫(九)- InputNumber 實現
- 從零實現Vue的元件庫(十)- Select 實現
- 從零實現Vue的元件庫(十一)- Date-picker 實現
- 從零實現Vue的元件庫(十二)- Table 實現
- 從零實現Vue的元件庫(十三)- Pagination 實現
- 從零實現Vue的元件庫(十四)- RadioGroup 實現
原創宣告: 該文章為原創文章,轉載請註明出處。