藉由 Vue h function 讓 Quasar Dialog 更簡單複用

鱈魚-avatar-img
發佈於Vue
更新於 發佈於 閱讀時間約 11 分鐘
鱈魚的魚缸搬家了!新家文章皆有重新修訂,歡迎來新家看看喔。(´▽`ʃ♡ƪ)
raw-image


大家好,我是鱈魚。(^∀^●)ノシ


Quasar 是一個基於 Vue 的全端框架,具體內容可以看看官方文檔介紹

不過目前我主要還是把 Quasar 當 UI 套件用。(╯▽╰ )


Quasar 的 Dialog 有一個很有趣的功能,在文檔稱為「Invoking custom component」,也就是可以以命令式(imperative)呼叫任何自定義的 Dialog 元件。


這在建立複雜的後台系統很方便,除了元件可以作為 Dialog 重複使用,更可以保持 template 乾淨,不會充滿大量與目前元件邏輯無關的 html 內容,讓程式碼更簡潔、內聚一點。


但是有個小困擾,文檔表示必須先將元件包裹於 q-dialog 並註冊 useDialogPluginComponent 相關參數。


也就是說我有一個 CreateForm 元件要變成 Dialog,要建立一個新元件:

CreateFormDialog.vue

<template>
<q-dialog ref="dialogRef" @hide="onDialogHide">
<create-form>
<!--
...content
... use q-card-section for it?
-->
</create-form>
</q-dialog>
</template>

<script setup>
import { useDialogPluginComponent } from 'quasar'
import CreateForm from './components/create-form.vue';

const props = defineProps({
// ...your custom props
})

defineEmits([
...useDialogPluginComponent.emits
])

const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()

function onOKClick () {
onDialogOK()
}
</script>


接著使用 $q.dialog 呼叫:

$q.dialog({
component: CreateFormDialog,
})

成功把 CreateForm 變成 Dialog 開啟了!\( ̄︶ ̄*\))


不過這樣會導致每有一個元件想以 Dialog 形式開啟,就會多一個 xxxDialog.vue 的變體元件,實在是有點麻煩且多餘。


但是我們知道,其實 Vue 內部的 VDOM 都是透過 h function 建立,所以其實我們可以利用 h function 簡化這個過程。

(h function 就是所謂的渲染函數,詳細說明可以看看官方文檔


h function 的寫法很簡單,其參數依順序如下:

  1. type:除了 html tag 外,也可以是 Vue 的元件。
  2. props:DOM 的參數,也可以是元件的 prop。
  3. children:此 DOM 的子元素,也可以是元件的 slot


import { h } from 'vue'

const vnode = h(
'div', // type
{ id: 'foo', class: 'bar' }, // props
[
/* children */
]
)


換句話說,剛剛的程式碼:

$q.dialog({
component: CreateFormDialog,
})


可以透過 h function 改寫成:

$q.dialog({
component: h(
QDialog,
{},
{ default: () => h(CreateForm) }
)
});


鱈魚:「這樣就不用一直建立 Dialog 變體元件了!(≧∇≦)ノ」

路人:「這樣程式碼看起來很可怕欸。(゚Д゚*)ノ」

鱈魚:「也是捏,讓我們封裝、重構一下。(。・∀・)ノ゙」


首先第一步,讓我們建立協助提取元件型別的實用 function。

src/types/utils.type.ts

import { DefineComponent } from 'vue';

/** 提取 Vue Component 之內部 props
*
* 會將 style、class、event 全部取出來
*/
export type ExtractComponentProps<TComponent> = TComponent extends new () => {
$props: infer P;
}
? P
: never;

/** 提取 Vue Component slots */
export type ExtractComponentSlots<TComponent> = TComponent extends new () => {
$slots: infer P;
}
? P
: never;


接著建立基於 Quasar 的 utils 工具。


主要有 2 個 function:

  • wrapWithDialog:用於將指定元件使用 QDialog 包起來。
  • openUsingDialog:將 wrapWithDialog 包裹後的元件,使用 Dialog 開啟。


src/common/utils-quasar.ts

import { Dialog, DialogChainObject, QDialog, QDialogProps } from 'quasar';
import { Component, h } from 'vue';
import { ExtractComponentProps, ExtractComponentSlots } from '../types';

/** 將 Vue SFC 元件包装為 QDialog,可以更簡單配合 $q.dialog 使用
*
* @param component Vue SFC 元件
* @param props SFC 內所有參數,包含 class、style、event 等等
* @param dialogProps QDialog 原本參數
*
* @example
* ```typescript
* const component = wrapWithDialog(EditForm, {
* data,
* onSubmit() {
* dialog.hide();
* },
* });
*
* const dialog = $q.dialog({ component });
* ```
*
* @example
* ```typescript
* $q.dialog({
* component: wrapWithDialog(
* BrandLog,
* {
* data,
* class: 'w-full'
* },
* {
* fullHeight: true,
* }
* ),
* });
* ```
*/
export function wrapWithDialog<Comp extends Component>(
component: Comp,
props?: ExtractComponentProps<Comp>,
dialogProps?: QDialogProps,
slots?: ExtractComponentSlots<Comp>
) {
return h(QDialog, dialogProps, {
default: () => h(component, props ?? {}, slots ?? {}),
});
}

/** 使用 Quasar Dialog 開啟元件
*
* @param component Vue SFC 元件
* @param props SFC 內所有參數,包含 class、style、event 等等
* @param slots SFC 插槽
* @param dialogProps QDialog 原本參數
* @returns
*
* @example
* ```typescript
* const dialog = openUsingDialog(EditForm, {
* data,
* onSubmit() {
* dialog.hide();
* },
* });
* ```
*/
export function openUsingDialog<Comp extends Component>(
component: Comp,
props?: ExtractComponentProps<Comp>,
dialogProps?: QDialogProps,
slots?: ExtractComponentSlots<Comp>
) {
return Dialog.create({
component: wrapWithDialog(component, props, dialogProps, slots),
});
}


現在我們可以把剛剛的程式碼:

$q.dialog({
component: h(
QDialog,
{},
{ default: () => h(CreateForm) }
)
});


改寫成這樣:

openUsingDialog(CreateForm)


是不是整潔很多啊!♪( ◜ω◝و(و

而且因為有加上型別推導,所以使用時會有很完整的型別提示。

raw-image


現在可以配合元件事件撰寫邏輯了。( •̀ ω •́ )✧

Vue 會自動將元件 emit 的事件名稱加上 on 前綴,所以:

  • update:modelValue 會變成 onUpdate:modelValue
  • cancel 會變成 onCancel


function openCreateForm() {
const dialog = openUsingDialog(CreateForm, {
'onUpdate:modelValue'(data) {
alert(`送出資料: ${data.name}`);
dialog.hide();
},
onCancel() {
dialog.hide();
}
});
}


以上用法讓大家參考參考,如果有問題還不吝指教,鱈魚感謝大家!( ´ ▽ ` )ノ

更完整的範例請見範例程式碼

avatar-img
17會員
14內容數
各種鱈魚滾鍵盤的雜紀
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
鱈魚的魚缸 的其他內容
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
有時候使用 Pinia 會遇到資料意外變更問題,這其實不是鳳梨的錯,讓我們看看怎麼回事吧。( ´ ▽ ` )ノ
到底要用 ref 還是 reactive 是一個很常見的問題,不過現在官方文檔推薦使用 ref 就行,所以也不是甚麼大問題就是了。( •̀ ω •́ )✧ 所以 reactive 真的沒用用途了嗎?這篇文章來記錄一下 reactive 的用法。
watch 不是不能用,而是在使用 watch 之前,先想想有沒有其他方案,真的沒有才用 watch。 千萬不要為了一時方便讓元件裡滿滿的 watch,因為容易產生難以追蹤的副作用,會讓資料流更加複雜。
如果 watch 沒有放在元件最外層,可能會導致元件 onUnmounted 後watch不會自動解除,至於該怎麼辦,就讓我們娓娓道來。( ´ ▽ ` )ノ
大家好,我是鱈魚。(^∀^●)ノシ Vue 3.3 終於新增了泛型元件(Generic Component),這讓我們可以在 TypeScript 環境中得到更準確的型別提示。( •̀ ω •́ )✧ 讓我們一起來看看吧!
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
有時候使用 Pinia 會遇到資料意外變更問題,這其實不是鳳梨的錯,讓我們看看怎麼回事吧。( ´ ▽ ` )ノ
到底要用 ref 還是 reactive 是一個很常見的問題,不過現在官方文檔推薦使用 ref 就行,所以也不是甚麼大問題就是了。( •̀ ω •́ )✧ 所以 reactive 真的沒用用途了嗎?這篇文章來記錄一下 reactive 的用法。
watch 不是不能用,而是在使用 watch 之前,先想想有沒有其他方案,真的沒有才用 watch。 千萬不要為了一時方便讓元件裡滿滿的 watch,因為容易產生難以追蹤的副作用,會讓資料流更加複雜。
如果 watch 沒有放在元件最外層,可能會導致元件 onUnmounted 後watch不會自動解除,至於該怎麼辦,就讓我們娓娓道來。( ´ ▽ ` )ノ
大家好,我是鱈魚。(^∀^●)ノシ Vue 3.3 終於新增了泛型元件(Generic Component),這讓我們可以在 TypeScript 環境中得到更準確的型別提示。( •̀ ω •́ )✧ 讓我們一起來看看吧!
你可能也想看
Google News 追蹤
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
這篇是我第一次在方格子發布的內容,開頭也有點不知道該怎麼起,就隨意打打當作閒聊了。
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
Vue.js是一種基於MVVM的前端JavaScript框架,類似的框架有React、Angular等。 架設環境 安裝Visual Studio Code(https://code.visualstudio.com/) 安裝Node.js(https://nodejs.org/en/
Thumbnail
樣板模式的定義極為簡單,卻是大型系統程式、WEB/APP應用框架的設計核心,完美展現設計模式的價值: 簡單、高效、強大。
Thumbnail
Vue Router 及 具名視圖,擺脫以往切換依賴 CSS display:none 跟 display:block 互相配合,有時還得搭配 z-index 來調整層級跟 opacity 透明度的麻煩,而 Vue Router 完美的解決了這棘手的問題,且能客製頁面想要呈現的擺飾。
Thumbnail
vocus 致力於讓創作者透過多元的變現方式展現你的創作動力,除了原有的訂閱、廣告、贊助功能,在去年正式推出了「數位商品」販售功能,讓你的創作能有更多樣化的呈現與變現方式。 為了讓創作者們能更輕鬆地上架數位商品,我們做了以下的功能更新,讓你在上架時更加順暢!
Thumbnail
在近期的沙龍改版升級中,非常感謝各位會員與創作者給予的回饋,不論是透過客服信箱、討論區、留言或是其他管道, vocus 產品團隊與營運夥伴皆會悉心傾聽會員們的聲音,並用心推出更好的服務,滿足各位會員的使用需求。
Thumbnail
Vocus 新增了沙龍功能,目的是為了取代原本的「專題」,我這篇就來分享一下我目前摸索沙龍的經驗
Thumbnail
vocus 團隊即刻推出全站會員自主升級沙龍的功能,以及全新的首頁與瀏覽頁面,話不多說,讓格編帶你開箱這次的新介面與功能吧! 還有「沙龍開箱」有獎徵文,你參加了嗎?
Thumbnail
大家好,我是woody,是一名料理創作者,非常努力地在嘗試將複雜的料理簡單化,讓大家也可以體驗到料理的樂趣而我也非常享受料理的過程,今天想跟大家聊聊,除了料理本身,料理創作背後的成本。
Thumbnail
哈囉~很久沒跟各位自我介紹一下了~ 大家好~我是爺恩 我是一名圖文插畫家,有追蹤我一段時間的應該有發現爺恩這個品牌經營了好像.....快五年了(汗)時間過得真快!隨著時間過去,創作這件事好像變得更忙碌了,也很開心跟很多厲害的創作者以及廠商互相合作幫忙,還有最重要的是大家的支持與陪伴🥹。  
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
這篇是我第一次在方格子發布的內容,開頭也有點不知道該怎麼起,就隨意打打當作閒聊了。
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
Vue.js是一種基於MVVM的前端JavaScript框架,類似的框架有React、Angular等。 架設環境 安裝Visual Studio Code(https://code.visualstudio.com/) 安裝Node.js(https://nodejs.org/en/
Thumbnail
樣板模式的定義極為簡單,卻是大型系統程式、WEB/APP應用框架的設計核心,完美展現設計模式的價值: 簡單、高效、強大。
Thumbnail
Vue Router 及 具名視圖,擺脫以往切換依賴 CSS display:none 跟 display:block 互相配合,有時還得搭配 z-index 來調整層級跟 opacity 透明度的麻煩,而 Vue Router 完美的解決了這棘手的問題,且能客製頁面想要呈現的擺飾。
Thumbnail
vocus 致力於讓創作者透過多元的變現方式展現你的創作動力,除了原有的訂閱、廣告、贊助功能,在去年正式推出了「數位商品」販售功能,讓你的創作能有更多樣化的呈現與變現方式。 為了讓創作者們能更輕鬆地上架數位商品,我們做了以下的功能更新,讓你在上架時更加順暢!
Thumbnail
在近期的沙龍改版升級中,非常感謝各位會員與創作者給予的回饋,不論是透過客服信箱、討論區、留言或是其他管道, vocus 產品團隊與營運夥伴皆會悉心傾聽會員們的聲音,並用心推出更好的服務,滿足各位會員的使用需求。
Thumbnail
Vocus 新增了沙龍功能,目的是為了取代原本的「專題」,我這篇就來分享一下我目前摸索沙龍的經驗
Thumbnail
vocus 團隊即刻推出全站會員自主升級沙龍的功能,以及全新的首頁與瀏覽頁面,話不多說,讓格編帶你開箱這次的新介面與功能吧! 還有「沙龍開箱」有獎徵文,你參加了嗎?