vue3 優雅的使用 useDialog

作者:隔壁老王 z

https://juejin.cn/post/7293173815181738022

在日常開發時,彈窗是一個經常使用的功能,而且重複性極高,你可能會遇到下面這些問題: 1、一個頁面內多個彈窗, 要維護多套彈窗狀態, 看的眼花繚亂 2、彈窗內容比較簡單,聲明變量 + 模板語法的方式寫起來比較麻煩

關於這些問題, 我首先想到的是應該弄一個即用即走的Dialog,不用去單獨維護它的狀態,使用Dialog({ xxx })這種形式去調用它,例如下面這種配置的方式:

Dialog({
    title: 'xxx',
    render: () => xxx
})

其中render可以是一個html字符串,也可以是jsx(需要配置對 jsx 的支持),這樣可以對內容區域實現自定義。

通過配置項調用

各大主流的 ui 庫基本都實現了這種調用方式:

之前沒有注意到Element-plusMessageBox可以使用 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,而是想使用模板,vuehooks工具庫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等狀態. 例如以上的例子中, 我們可以輕鬆的處理buttonloading狀態 (不用再額外聲明變量), 用PromiseDialogUI和狀態實現了內聚. 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