用 Nuxt UI 做後台版面,大部分力氣不會花在刻畫面上——側邊欄的收合、拖曳調寬、行動版的抽屜行為,元件都做掉了。
剩下要自己決定的事不多,但其中三個做錯時不會有任何錯誤訊息。這篇只記這三個。
一、漏掉根元件,Toast 與 Tooltip 靜默失效
UApp 不只是個容器,它承載了整組疊加層:通知、提示氣泡、Modal 的掛載點、以及元件內建文字的語系。
沒有包它的話,useToast() 呼叫下去不會拋錯,只是什麼都不會出現。這個症狀最惡劣的地方在於它會被誤判成「通知邏輯沒被觸發」,於是接下來一小時都在查錯的那一端。
<script setup lang="ts">
import { zh_tw } from '@nuxt/ui/locale'
</script>
<template>
<UApp :locale="zh_tw">
<NuxtPage />
</UApp>
</template>
順帶一提,locale 傳進去之後,元件內建的文字(像下拉選單的「無結果」)才會是中文——這是少數幾個「不設定就是英文」而不是「不設定就壞掉」的地方,很容易上線後才被使用者發現。
二、桌機版與行動版不要用 v-if 切
同一組導覽,桌機是常駐側邊欄、行動版是抽屜。最直覺的寫法是依螢幕寬度切換兩個元件:
<!-- 螢幕跨過斷點時,元件被銷毀重建 -->
<UDashboardSidebar v-if="!isMobile" />
<UDashboardSidebarMobile v-else />
問題不在渲染結果,在於 v-if 會銷毀元件實例。使用者調整視窗大小、或裝置旋轉,側邊欄的展開狀態、捲動位置、甚至展開中的子選單就全部歸零。開發時很少轉螢幕,所以這個問題通常由使用者回報。
你可能會想:那包一層快取元件的機制不就解決了?可以,但你等於為了修一個不該存在的問題,多引入一套需要自己管理生命週期的東西——而且它保住的只是元件狀態,DOM 該有的捲動位置與焦點仍然會掉。真正的問題是這裡本來就不需要兩個元件。
用 CSS 控制顯示就沒有這件事——元件一直活著,只是有沒有被畫出來:
<UDashboardSidebar collapsible resizable class="hidden lg:flex" />
同樣的道理,UDashboardGroup 的 unit 建議設 rem:側邊欄寬度跟著使用者的字級縮放走,而不是釘死在像素上。
三、選單資料要有型別
導覽選單是最容易被寫成「一坨物件陣列」的地方,而它同時也是最常被改的地方。用官方的型別標注起來,打錯的屬性名當場就會被抓到:
import type { NavigationMenuItem } from '@nuxt/ui'
const menuItems: NavigationMenuItem[] = [
{ label: '儀表板', icon: 'i-lucide-layout-dashboard', to: '/' },
{
label: '使用者',
icon: 'i-lucide-users',
children: [
{ label: '列表', to: '/users' },
{ label: '新增', to: '/users/new' },
],
},
]
沒有型別的話,icon 打成 iconName 這種錯誤不會有人告訴你——圖示只是沒出現,而版面照樣是完整的。
這三個的共同點
| 做錯 | 你看到的 | 你會去查的 |
|---|---|---|
沒包 UApp | 通知沒跳出來 | 通知的觸發邏輯 |
用 v-if 切版面 | 狀態偶爾歸零 | 狀態管理 |
| 選單沒有型別 | 圖示沒出現 | 圖示名稱、icon 套件 |
三個的實際原因都不在你會去查的那一欄。這也是為什麼它們值得先寫下來——它們不是難,是不會叫。