MUKI AI Summary
在開發模組化功能時,使用大量的 v-if 來動態渲染 Vue 元件會導致程式碼重複且難以維護。為了解決這個問題,可以透過動態渲染元件和動態導入模組來簡化邏輯。首先,使用 Barrel Files 將模組集中管理,接著在 App.vue 中根據 moduleId 渲染對應的 Vue 元件,避免用大量的 v-if 判斷。進一步優化可以使用 Vue 的 動態元件搭配 Mapping 表格來渲染模組,這樣可以大幅減少重複的程式碼。
為了進一步簡化,推薦使用 Vite 的 import.meta.glob 功能,這樣可以自動批量導入模組,無需手動更新 Mapping 表格或 index.ts 檔案。透過這種方式,不僅程式碼更簡潔,未來新增或修改元件時只需將新檔案放入目錄即可,無需更改現有邏輯,讓開發和維護更加高效。這種動態渲染方式特別適合需要處理大量條件邏輯的場景。...
最近在開發一個模組化的功能,遇到要根據不同的條件動態渲染元件的狀況,雖然使用大量的 v-if
也能解決這問題,但程式碼重複性很高,也不利於維護,重點是也不美觀 XXD。所以我們可以用動態渲染元件搭配動態導入模組,讓程式碼好維護並好擴展。
實際情況
以下是我實際開發時碰到的狀況,節錄片段程式碼與大家分享。
▼ 這是我的模組目錄結構,現在的需求是要在 App.vue 匯入這些模組
src/ ├─ components/ │ ├─ A1-1-2.vue │ ├─ A1-2-2.vue │ ├─ A1-3-3.vue │ ├─ B1-1-1.vue │ ├─ C1-1-2.vue │ └─ index.ts ├─ App.vue
▼ 我先用 Barrel Files 將模組集中在 index.ts 統一管理 (關於 Barrel Files 的介紹,可以參考我寫的使用 Barrel Files 的好處與大多數人不推薦的原因
export { default as A112 } from './A1-1-2.vue' export { default as A122 } from './A1-2-2.vue' export { default as A133 } from './A1-3-3.vue' export { default as B111 } from './B1-1-1.vue' export { default as C112 } from './C1-1-2.vue'
▼ 接著在 App.vue 匯入元件,並根據 moduleId
來渲染對應的 Vue 元件,一開始先使用 v-if
暴力解
<template> <div> <Module.A112 v-if="moduleId === 'a1-1-2'" /> <Module.A122 v-if="moduleId === 'a1-2-2'" /> <Module.A133 v-if="moduleId === 'a1-3-3'" /> <!-- 其他模組 --> </div> </template> <script lang="ts" setup> import * as Modules from '@/components/home/module' </script>
問題點應該蠻明確的,一來程式碼重複性高,二來難以擴展,因為新增模組時都要手動編輯,容易出錯成本也高;再來就是模組越多時,真的看得很阿雜!
解法一:動態渲染元件
▼ 像上面這種重複性高的元件,我們可以做一個 Mapping 表格,搭配 Vue 的動態元件 <component>
來渲染對應的模組
<template> <div> <component :is="moduleMapping[moduleId]" /> </div> </template> <script lang="ts" setup> import * as Module from '@/components/home/module' const moduleMapping = { 'a1-1-2': Module.A112, 'a1-2-2': Module.A122, 'a1-3-3': Module.A133, 'b1-1-1': Module.B111, 'c1-1-2': Module.C112 }; </script>
透過 Mapping 映射表與 <component>
動態元件,就不需要用一堆的 v-if
判斷條件,可以大幅簡化程式碼。
但這樣做只是把 v-if
的邏輯判斷放到 Mapping 映射表,我還是要同時維護 Mapping 映射表以及用來管理模組的 index.ts 檔案,感覺只是換湯不換藥。
所以接下來推薦第二種解法:動態元件搭配動態模組,真正做到全自動化
解法二:動態渲染元件與動態管理模組
▼ 回到剛剛的 index.ts,我是用 Barrel Files 將模組集中管理。但未來只要新增模組,這邊就要再更新一次,Mapping 映射表也要再寫一次,非常麻煩
export { default as A112 } from './A1-1-2.vue' export { default as A122 } from './A1-2-2.vue' export { default as A133 } from './A1-3-3.vue' export { default as B111 } from './B1-1-1.vue' export { default as C112 } from './C1-1-2.vue'
幸好 Vite 有一個 import.meta.glob
能幫助我們以動態方式批量導入模組
使用 import.meta.glob 批量導入模組
▼ 將 index.ts
檔案修改為以下程式碼
interface Modules { [key: string]: { default: unknown } } // 以相對路徑和萬用字元(*)來匹配檔案 // 預設情況下,模組是延遲載入的。如果需要立即載入,可以設定 eager: true: const modules: Modules = import.meta.glob('./*.vue', { eager: true }) export default Object.keys(modules).reduce((acc: Record<string, unknown>, path: string) => { // 視情況處理匯出的 moduleName const moduleName = path.replace('./', '').replace('.vue', '').toLowerCase() acc[moduleName] = modules[path].default return acc }, {})
▼ 可以印出 acc
來確認物件名稱是否如你所預期
▼ 回到 App.vue,修改 import
內容,並大膽的刪除 Mapping 映射表
<script lang="ts" setup> // 原本的 import import * as Module from '@/components/home/module' // 修改後的 import import Modules from '@/components/home/module' // 刪除以下內容 const moduleMapping = { 'a1-1-2': Module.A112, 'a1-2-2': Module.A122, 'a1-3-3': Module.A133, 'b1-1-1': Module.B111, 'c1-1-2': Module.C112 }; </script>
▼ 原本的 <component>
是用 Mapping 映射表判斷,現在改成用 Modules 判斷
<template> <div> <component :is="Modules[moduleId]" /> </div> </template>
動態元件 <component> 需要加入 v-if 判斷條件嗎
在這個範例中,我的 Modules[moduleId]
如果找不到對應的元件,Vue 會自動渲染為空,不會造成錯誤。此外,我也確定 moduleId
一定是個有效值,也就是在 Modules 中一定有對應的元件,所以不一定要加上 v-if
檢查。
▼ 但如果習慣使用 v-if
謹慎處理,一樣可以加入 v-if
做二次判斷
<template> <div> <component v-if="Modules[moduleId]" :is="Modules[child.moduleId]" /> </div> </template>
好處是避免在 moduleId
無效時出現 Vue 的警告訊息,且程式碼更加有防禦性,能對錯誤情況有明確的處理。
至此,將模組和元件都動態處理後,就不用再寫繁瑣的程式碼做各種判斷了。
完整的程式碼
最後附上完整的程式碼供大家參考
▼ 使用 import.meta.glob
動態管理模組
interface Modules { [key: string]: { default: unknown } } const modules: Modules = import.meta.glob('./*.vue', { eager: true }) export default Object.keys(modules).reduce((acc: Record<string, unknown>, path: string) => { // 視情況處理匯出的 moduleName const moduleName = path.replace('./', '').replace('.vue', '').toLowerCase() acc[moduleName] = modules[path].default return acc }, {})
▼ 使用 <component>
動態渲染元件
<template> <div> <component v-if="Modules[moduleId]" :is="Modules[child.moduleId]" /> </div> </template> <script lang="ts" setup> import Modules from '@/components/home/module' </script>
小結
透過這種方式,可以解決手動撰寫重複程式碼的問題,讓元件渲染邏輯變得更加靈活且可維護,且程式碼也變得非常簡潔乾淨。未來若需要新增或修改元件,只需將新檔案放入目錄,無需改動現有邏輯,敲~級方便!。
這種動態渲染的方式,尤其適用於需要處理大量條件邏輯的場景,可以讓開發更高效,維護更輕鬆!
以上有任何問題歡迎留言給我,如果你覺得這篇文章對你有幫助,也別忘了分享給你的朋友唷 😉