vue3 優雅的使用 useDialog
作者:隔壁老王 z
https://juejin.cn/post/7293173815181738022
在日常開發時,彈窗是一個經常使用的功能,而且重複性極高,你可能會遇到下面這些問題: 1、一個頁面內多個彈窗, 要維護多套彈窗狀態, 看的眼花繚亂 2、彈窗內容比較簡單,聲明變量 + 模板語法的方式寫起來比較麻煩
關於這些問題, 我首先想到的是應該弄一個即用即走的Dialog
,不用去單獨維護它的狀態,使用Dialog({ xxx })
這種形式去調用它,例如下面這種配置的方式:
Dialog({
title: 'xxx',
render: () => xxx
})
其中render
可以是一個html
字符串,也可以是jsx
(需要配置對 jsx 的支持),這樣可以對內容區域實現自定義。
通過配置項調用
各大主流的 ui 庫基本都實現了這種調用方式:
-
Ant Design Vue 的 Modal
-
Element-plus 的 MessageBox
之前沒有注意到Element-plus
的MessageBox
可以使用 jsx,大部分場景下, 用它來代替Dialog
還是很方便的。示例代碼:
<script lang="jsx" setup>
import { reactive, ref } from 'vue';
import { ElMessageBox, ElForm, ElFormItem, ElInput } from 'element-plus'
const formRef = ref(null)
const form = reactive({ height: '', width: '' })
const rules = reactive({
height: {
required: true,
trigger: 'blur'
},
width: {
required: true,
trigger: 'blur'
}
})
function openMessageBox() {
ElMessageBox({
title: 'Message',
showCancelButton: true,
// message如果不是函數形式 綁定ref會失敗
message: () =>
<ElForm
ref={formRef}
model={form}
rules={rules}
>
<ElFormItem label="height" prop="height">
<ElInput v-model={form.height}></ElInput>
</ElFormItem>
<ElFormItem label="width" prop="width">
<ElInput v-model={form.width}></ElInput>
</ElFormItem>
</ElForm>
,
beforeClose: (action, instance, done) => {
console.log(action, instance)
formRef.value && formRef.value.validate(status => {
console.log('校驗狀態: ', status)
if (status || action==='cancel') done()
})
}
})
}
</script>
<template>
<div class="container">
<button @click="openMessageBox">
打開messagebox
</button>
</div>
</template>
效果如下:
vueuse 的 createTemplatePromise
如果你不想使用jsx
,而是想使用模板,vue
的hooks
工具庫vueuse
中提供了 createTemplatePromise 這個函數用來創建對話框、模態框、Toast 等,並且完全使用的是template
的方式,因此自定義程度更高,並且沒有任何額外成本 (不需要 jsx)。下面是一個createTemplatePromise
結合el-dialog
的例子 (當然也可以結合其它的dialog
或者自定義dialog
):
<script lang="jsx" setup>
import { createTemplatePromise } from '@vueuse/core'
import { ElDialog, ElButton } from 'element-plus'
const TemplatePromise = createTemplatePromise()
async function open(idx) {
console.log(idx, 'Before')
const result = await TemplatePromise.start('Title', `Hello ${idx}`)
console.log(idx, 'After', result)
}
function asyncFn() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('ok')
}, 1000)
})
}
</script>
<template>
<div class="container">
<button @click="open(1); open(2)">
打開兩個彈框
</button>
</div>
<TemplatePromise v-slot="{ resolve, args, isResolving }">
<el-dialog :modelValue="true" :title="args[0]">
<div>Dialog {{ args[1] }}</div>
<p>可以打開控制檯查看logs</p>
<div class="flex gap-2 justify-end">
<el-button @click="resolve('cancel')">
取消
</el-button>
<el-button type="primary" :disabled="isResolving" @click="resolve(asyncFn())">
{{ isResolving ? 'loading...' : '確認' }}
</el-button>
</div>
</el-dialog>
</TemplatePromise>
</template>
效果如圖:
Promise 化 Dialog 的優點
這樣有小夥伴可能會說, 這看起來和原來用dialog
也沒有很大區別啊, 也要寫模版 + 函數方法. 那麼讓dialog
變成這樣有什麼好處呢? 1、最大的好處是彈窗變得可編程了, 通過函數調用的方式來控制UI
. 不用再單獨聲明變量控制顯隱, 也不用單獨再去控制按鈕的禁用
、loading
等狀態. 例如以上的例子中, 我們可以輕鬆的處理button
的loading
狀態 (不用再額外聲明變量), 用Promise
讓Dialog
的UI
和狀態實現了內聚. 2、相比第一種方式, 對 UI 的可自定義程度更高.
demo 地址
代碼地址:https://github.com/plutda/functional-dialog
在線預覽地址:https://plutda.github.io/functional-dialog
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/B0TWs1sKB7Qbf-yDUdCW2w