引言
頁面引用彈出框組件是經常碰見的需求,如果強行將彈出框組件放入到頁面中,雖然功能上奏效但沒有實現組件與頁面間的解耦,非常不利于后期的維護和功能的擴展.下面舉個例子來說明一下這種做法的弊端.
@click="openModal()">點擊
:is_open="is_open" @close="close()"/>
import Modal from "../components/Modal/Modal";//外部引入的彈出框組件export default { components: { Modal, }, data(){ return { is_open:false //控制彈出框關閉或打開 } }, methods: { openModal() { //顯示彈出框 this.is_open = true; }, close(){ //子組件觸發的事件,關閉彈出框 this.is_open = false; } },};
Modal是外部引入的彈出框組件,父組件通過is_open
來控制彈出框的隱藏和顯示.仔細分析上述結構存在的問題如下.
?Modal組件被硬編碼,強行在父組件的components
里面注冊并在父組件的模板中渲染.設想一下,一個彈出框組件就需要在父組件中寫一次,5個彈出框也都要在父組件的模板里寫五個.這樣會讓父組件的頁面結構變的復雜不利于閱讀,其次彈出框組件應該與父組件解耦,它不應該寫死在父組件的模板中.?父組件需要單獨設置一個狀態
is_open
來控制彈出框的顯示和隱藏,假如父組件需要引入多個彈出框,那勢必也要定義多個狀態來對彈出框進行控制.
為了實現彈出框和父組件的解耦,最理想的方式是運用函數式編程的思想,在父組件內只需要調用一個函數就可以讓彈出框顯示出來,接下來看一下如何實現.
彈出框組件的處理
我們接下來實現一個代碼十分簡單但功能強大的工具函數,借助它就可以將彈出框組件封裝起來.如果父組件需要使用哪個彈出框組件直接調用函數就能輕松顯示或者隱藏.
實現
import Vue from 'vue';export const createModal = (Component, props) => { const vm = new Vue({ render: (h) => h(Component, { props, }), }).$mount(); document.body.appendChild(vm.$el); const ele = vm.$children[0]; ele.destroy = function() { vm.$el.remove(); ele.$destroy(); vm.$destroy(); }; return ele;};
?Component
就是父組件調用的彈出框組件,在這里作為參數傳入.props
是最終傳遞給彈出框組件內部的props
?new
?一個?Vue
實例,render
屬性對應的函數里,h
的作用是將彈出框組件變成虛擬dom?$mount
一定要調用,它會將虛擬dom轉換成真實的dom元素?vm.$el
就是對應到傳入的彈出框組件Component
所渲染的真實dom,將它掛載到body下面,此時頁面就會顯示出彈出框?光顯示出彈出框還不夠,我們還需要給彈出框組件創建一個銷毀方法destroy
,其中vm.$children[0]
對應的就是彈出框組件的vue
實例,可以調用destroy
方法銷毀.最后將該實例返回供外部調用,外部通過該實例就可以調用彈出框組件內部的屬性和方法.
應用
作為測試Demo
,彈出框組件結構如下,模板內容十分簡單.渲染一個頭部標題title
和內容content
.定義兩個方法show()
和hide()
來操作is_open
狀態來控制彈出框的顯示和隱藏.
class="modal" v-if="is_open">
class="content">
class="close" @click="hide()">close
class="title">{{ title }}
{{ content }}
export default { props: ["title", "content"], data() { return { is_open: false, }; }, methods: { show() { this.is_open = true; }, hide() { this.is_open = false; }, },};lang="scss" scoped>
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
.content {
width: 200px;
height: 200px;
background-color: #fff;
margin: 0px auto;
margin-top: 200px;
text-align: center;
font-size: 14px;
color: #333;
padding: 5px;
.title {
margin-bottom: 20px;
font-size: 16px;
}
.close {
text-align: right;
}
}
}
頁面組件
class="test-v2"> @click="openModal()">點擊
import Modal from "../../components/Modal/Modal";import { createModal } from "../../util/Modal";export default { methods: { openModal() { this.ele = createModal(Modal, { title: "彈出框", content: "內容", }); this.ele.show(); } },};
頁面父組件通過調用createModal
方法能獲取到彈出框組件Modal
的實例this.ele
.通過this.ele
就可以拿到彈出框組件內部的所有屬性和方法,包括顯示show()
和隱藏hide()
.
?經過上方一改造,實現了彈出框組件和父組件之間的解耦.彈出框組件不需要在父組件中注冊和模板內渲染.?如果父組件需要傳遞數據給彈出框組件,可以借助createModal
第二個參數對象,它最終會以props
的形式注入到彈出框組件的內部.?show()
和hide()
方法都是彈出框內部定義的,父組件可以直接調用控制其顯示隱藏.另外頁面銷毀時要調用一次this.ele.destroy()
,防止內存泄漏.
頁面效果

從最終的dom結構圖可以清晰的看到彈出框掛載在body
的下面,而非頁面組件內部.這樣在對彈出框定義一些與css定位相關的樣式時就輕松方便的多,不會受到頁面組件的影響和干擾.
延伸
通過上面對彈出框的講解我們還可以在此基礎做很多其他的事情,比如對消息提示框的處理.
消息提示框也屬于彈出框.最好的實踐方式是,只需要寫一行代碼?Alert("Hello world")
,頁面上就會立馬彈出消息提示?Hello world
.效果如下.

實現
父頁面結構如下,調用Alert()
函數,頁面就會顯示提示框.
class="test-v2"> @click="alert()">Alert
import { Alert } from "../../util/Modal";export default { methods: { alert() { Alert("Hello world"); }, },};
Alert
函數實現如下.
const alert_array = []; //用來存儲彈出框的實例export const Alert = (msg, duration = 3000) => { let top = 100; //默認距離頂部100px if (alert_array.length > 0) { const index = alert_array.length; top = top + index * 50; } const ele = createModal(AlertComponent, { title: msg, top, }); alert_array.push(ele); const timer = setTimeout(() => { clearTimeout(timer); const index = alert_array.indexOf(ele); index !== -1 && alert_array.splice(index, 1); ele.destroy(); }, duration);};
?AlertComponent
是自定義的消失提示框組件(需要引入),調用createModal()
獲取每個提示框的實例存儲在數組alert_array
中.?點擊一次按鈕出現一個消息提示框,點擊第二次按鈕時,第二個提示框應該出現在第一個框的下面,因此需要根據數組alert_array
動態計算絕對定位的top值,在創建彈出框實例時作為參數傳進去.?定時器控制默認3秒后移除彈出框.
AlertComponent
消息提示框組件內容如下.初始給top_value
賦值this.top - 30
,后來在mounted
中再將this.top
賦值一次,就是為了實現提示框出現時從上往下滑動的動畫效果.
class="alert-component"
:style="{ top: `${top_value}px`, opacity: opacity }"
>
{{ title }}
export default { props: ["title", "top"], data() { return { top_value: this.top - 30, opacity: 0, }; }, mounted() { const timer = setTimeout(() => { clearTimeout(timer); this.top_value = this.top; this.opacity = 1; }); },};.alert-component { height: 20px; border-radius: 4px; position: absolute; min-width: 300px; left: 50%; transform: translateX(-50%); background-color: #f0f9eb; color: #67c23a; align-items: center; padding: 10px 16px; transition: all 0.25s linear; opacity: 0;}
結尾
借助createModal
工具函數,不僅可以做消息提示框,另外包括消息確認框,動態的表單模態框都可以實現進一步的封裝簡化處理.當彈出框與頁面實現解耦后,整體的代碼邏輯會變得更加清晰,對后期維護和擴展都有巨大的好處.