標籤:

如何使用vue.js構造modal(彈窗)組件?

按照示例使用vue.component 構造一個modal組件,包括模版,還有一些方法

但只能在父層模版里預先加上&&標籤,才能正確渲染

如果是動態添加&標籤是沒有效果

問題就是,構造一個modal組件,如何在需要的時候動態非同步的插入到dom里


為什麼一定要非同步插入?

其實以前也有一些用戶跟我糾結過這個問題,他們覺得一定要在需要的時候創建這個組件才是符合他們思維的做法。在我看來,這是沒有理解『狀態驅動的界面』的一種表現。

傳統的命令式 (Imperative) 的思維寫出來的代碼:

$(".open-modal").on("click", function () {
var modal = new Modal()
modal.$appendTo("body")
modal.open()
})

// 在 modal 內部還要處理關閉、銷毀自身的邏輯

狀態驅動的思維寫出來的代碼:

this.showModal = true

// 關掉
this.showModal = false

哪個乾淨,哪個容易理解、容易測試、容易維護?

從模板的角度來看:在父模板里直接寫入 & 標籤,那麼這個 modal 渲染的位置是清晰明確的,你看一眼父模板就知道,哦,這裡可能會有個 modal,也就是說,你的模板描述了最終可能渲染出來的 DOM 結構。但命令式思維下非同步添加的 modal,你看模板的時候是根本看不見的,你的模板和最終的 DOM 結構沒有可靠的映射關係,因為你完全可能隨手把 modal 插到任何地方。你覺得這兩者哪個更容易維護?

題主可能會覺得總是渲染 & 不太效率。官網示例裡面的 modal 用的是 v-show,換成 v-if 就好了。v-if 和 v-show 的區別在於 v-if 是真正的 conditional rendering,如果初始狀態是 false,它什麼都不會幹。

另外一個情況是,我們可能需要在一個嵌套了很多層的子組件裡面觸發 modal。這種情況下,你應該把 modal 放在根組件裡面,然後從子組件觸發一個事件上去。

最後我得說,其實『狀態驅動』才是 Vue 的精髓所在。


彈出型的組件,無論是 Dialog、Notification、Toast 還是其它的,都有一些共通的問題需要解決:

1. 多個彈層同時出現時,z-index 的順序

2. 多個彈層同時出現時,共享遮罩層

3. 是否可以自我管理,典型的,定時或者點擊遮罩層關閉彈層。

4. 有些彈層需要保留狀態,比如你編輯某篇文章,不小心點叉了,再打開還在;而有些需要每次打開都是新的,比如後台中創建某個條目的彈出式表單,你創建過一個條目,下一次再開這個彈層,不應該保留上一個條目的信息

5. 組件放在哪,是否單例,這個和第4點放一起說

6. 如何支持快捷方式,比如喜聞樂見的掛在一個全局方法上,用時創建一個,關閉後銷毀

7. 模態,阻止滾動

這些都是之前自己寫組件庫時遇到的問題,分享一點自己的實踐~

1. z-index 的管理

通過設置 position 為 fixed 或 absolute 的方式製作彈層,多個彈層同時打開時,默認是以 dom 樹中的位置來確定遮照次序的。當然,我們可以手動設置 z-index,但這麻煩不說,還容易出錯。

我的做法是設置一個彈層基類組件,比如叫 Popup,更上層的 Dialog、Toast 之類都是基於它開發的。這個 Popup 組件保持一個單例計數器,比如這樣

&

3. 外部觸發與自我觸發的折中

拿最常見的需求來說,Dialog 需要能夠點擊遮罩層關閉。Vue 1.0 中通過 sync 修飾符很容易做到內外部共享狀態,但 2.0 中已經取消了 sync,可能是為了更清晰的狀態變化管理,但我覺得 sync 在一些特殊場合確實很有用。

那麼現在,一個典型的做法是完全交由外部來控制 Dialog 的開關,比如父組件通過 prop 設置一個布爾值 open,Dialog 發覺遮照被點擊時,可以向上拋出個 click-mask 事件,由父組件來決定是否可以關閉。

其實通過 v-model 也可以實現類似 sync 的功效,我不確定語義上是否一定準確,點擊遮照關閉對話框也算是用戶輸入,觸發一個 input 事件是合理的,但類似 Toast 2 秒後關閉,這可能說不通。但是共享狀態後,內外部都可以控制,實在是很方便,這個可以權衡?

4. 彈層每次打開,是否應該重繪。

我的做法是暴露一個 prop 布爾值 auto-reset 讓使用者決定。如果需要每次都重回,就將 open 綁在 v-if 上,否則綁在 v-show 上:

&