[DIY] 設計一個可回傳 Promise 的 Dialog 元件方法
有用過 sweetalert2 的話,應該會喜歡可以同步等待對話框回傳值的方式, 這裡做一個 Vue2 元件,呼叫該元件的方法會彈出對話框等待使用者輸入,並且回傳 Promise, 如此一來就能夠在同一個函式當中處理使用者輸入值。
Dialog 元件設計原理:
元件方法 GetConfirm() 顯示 Dialog 元件並回傳一個 Promise,。 設置watcher讓元件取得使用者輸入後 resolve promise 得利於上述元件的設計,實際上的效益是將複雜度封裝到子元件裡面(watcher移動到元件內), 如此不需在上層元件撰寫使用者輸入取值的監視邏輯, 讓我們得以在上層元件直接 await GetConfirm 同步取得值進行操作。
這個概念的用途非常廣,例如 Vue router 的 component route guard,在離開表單頁面前跳出使用者確認的 Dialog。
Vue3 實作 <template>
<v-dialog v-model="dialog" v-bind="$attrs">
<slot v-bind="{ Resolve }"></slot>
</v-dialog>
</template>
<script setup>
import { ref } from "vue";
const dialog = ref(false);
let resolve = null;
const Resolve = (v) => {
resolve(v);
dialog.value = false;
};
const GetResult = async () => {
dialog.value = true;
return new Promise((res) => (resolve = res));
};
defineExpose({ GetResult, Resolve });
</script>
[舊]Vuejs 實作 <button id="xBtn">執行測試</button>
<div id="xApp" class="modal" :style="{display: dialog?'block':'none'}">
<div class="modal-content">
<span class="close">Test Modal</span>
<p>The value selected will resolve by promise.</p>
<button @click="choose(1)">1</button>
<button @click="choose(2)">2</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
let data = { result: null, dialog: false }
let dialog = new Vue({
el: '#xApp',
data:() => data,
methods: {
getConfirm() {
// 先清空 result (避免兩次選中一樣的值無法觸發 watcher)
this.result = null // open dialog
this.dialog = true return new Promise((resolve, reject) => {
try {
const watcher = this.$watch(
// 設置監視的對象為 result
() => this.result ,
// 一旦 result 的值有改變,就 resolve promise,並啟動下一輪 watcher (newVal) => resolve(newVal) && watcher()
)
} catch (error) {
// 如果出錯就 reject promise
reject(error)
}
})
},
choose(value) {
// 為 result 設置值觸發 watcher 解開 promise
this.result = value // 關閉 dialog
this.dialog = false
}
}
})
document.getElementById('xBtn')
.addEventListener( 'click', async e => alert( await dialog.getConfirm() )
);
</script>
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content */
.modal-content {
background-color: #fefefe;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}
[舊]Vue-next 實作 這裡使用 vue-next/setup/quasar/typescript