Framework Usage 規範
目標讀者: 前端開發者、AI 助手
本文檔定義參考實作(
src/)應如何使用框架(lib/)提供的組件與工具。 這些規範確保應用程式具有一致的程式碼品質與可維護性。
目錄
1. 設計原則
核心原則
優先使用框架(lib/)提供的組件與工具,避免直接使用第三方套件
這確保:
- 一致性 - 所有頁面使用相同的 UI 語言
- 主題支援 - 自動支援 DaisyUI 30+ 主題
- 國際化 - 內建 i18n 支援
- 可維護性 - 統一的 API 設計
使用優先順序
1. lib/ 組件/工具 (優先)
↓ 如果 lib/ 沒有提供
2. 提出需求,擴展 lib/
↓ 如果時間緊迫
3. 在 src/ 中實作,標記 TODO 待遷移
2. UI 組件使用規範
規則
MUST use lib/components/*, NEVER use raw HTML or third-party UI libraries directly
// ❌ NEVER - 直接使用 HTML
<input type="text" className="..." />
<select className="...">...</select>
<button className="...">Submit</button>
// ❌ NEVER - 直接使用第三方 UI 組件
import { Button } from 'some-ui-library'
import { TextField } from '@mui/material'
// ✅ ALWAYS - 使用 lib/components
import { Input, Select, Button } from '@appfuse/appfuse-web'
<Input label="Customer Name" />
<Select label="Category" options={options} />
<Button color="primary">Submit</Button>
組件對照表
表單場景 - 使用 lib/form
在 react-hook-form 表單中,使用 @appfuse/appfuse-web/form 的整合組件:
| 需求 | 使用 | 導入路徑 |
|---|---|---|
| 文字輸入 | Input | @appfuse/appfuse-web/form |
| 多行文字 | Textarea | @appfuse/appfuse-web/form |
| 下拉選單 | Select | @appfuse/appfuse-web/form |
| 複選框 | Checkbox | @appfuse/appfuse-web/form |
| 單選按鈕 | Radio | @appfuse/appfuse-web/form |
| 開關 | Toggle | @appfuse/appfuse-web/form |
| 標籤輸入 | TagInput | @appfuse/appfuse-web/form |
| 檔案上傳 | FileInput | @appfuse/appfuse-web/form |
| 媒體上傳 | MediaInput | @appfuse/appfuse-web/form |
| 富文本編輯 | RichTextEditor | @appfuse/appfuse-web/form |
獨立展示 - 使用 lib/components
非表單場景或獨立展示,使用 @appfuse/appfuse-web/components:
| 需求 | 使用 | 導入路徑 |
|---|---|---|
| 按鈕 | Button | @appfuse/appfuse-web/components |
| 資料表格 | DataTable | @appfuse/appfuse-web/components |
| 虛擬表格 | VirtualTable | @appfuse/appfuse-web/components |
| 圖示 | Icon | @appfuse/appfuse-web/components |
| 分頁標籤 | Tabs | @appfuse/appfuse-web/components |
| 可收合卡片 | CollapsibleCard | @appfuse/appfuse-web/components |
| 下拉選單 | Dropdown | @appfuse/appfuse-web/components |
| 圖表 | LineChart, BarChart, etc. | @appfuse/appfuse-web/components |
注意:
lib/components/data-input/*是基礎 UI 組件,在表單場景應使用lib/form/*的整合版本。
文檔參考
- 元件總覽 - 組件索引
- lib/components/README.md - 組件庫原始碼 README
- 各組件目錄下的
README.md- 詳細 API 文檔
3. 表單組件使用規範
規則
MUST use lib/form/* for react-hook-form integration, NEVER use lib/components/data-input/* with manual integration
lib/form 是建立在 lib/components/data-input 之上的整合層,提供:
- react-hook-form 整合(
useController) - 自動 i18n 翻譯(label)
- 資料轉換(parse/format)
- 錯誤訊息國際化
// ❌ NEVER - 手動整合 react-hook-form
import { Input } from '@appfuse/appfuse-web'
import { useController } from 'react-hook-form'
function MyForm() {
const { field } = useController({ name: 'email', control })
return <Input value={field.value} onChange={field.onChange} />
}
// ✅ ALWAYS - 使用 lib/form 整合組件
import { Input, Select, Checkbox } from '@appfuse/appfuse-web/form'
function MyForm() {
return (
<>
<Input name="email" control={control} label="Email" />
<Select name="category" control={control} label="Category" options={options} />
<Checkbox name="agreed" control={control} label="I agree" />
</>
)
}
陣列欄位(useFieldArray)
對於動態陣列欄位(如地址列表、聯絡人列表),使用 useFieldArray 搭配 lib/form 組件:
// ✅ ALWAYS - 陣列欄位使用模式
import { useFieldArray, type Control, type FieldErrors } from 'react-hook-form';
import { Input } from '@appfuse/appfuse-web/form';
import { Button } from '@appfuse/appfuse-web/components';
interface AddressListProps {
control: Control<any>;
name: string;
errors?: FieldErrors;
}
function AddressList({ control, name, errors }: AddressListProps) {
const { fields, append, remove } = useFieldArray({ control, name });
return (
<div>
{fields.map((field, index) => (
<div key={field.id}>
{/* ✅ 使用 lib/form 組件,自動處理欄位錯誤 */}
<Input
name={`${name}.${index}.address`}
control={control}
label="Address"
required
/>
<Button onClick={() => remove(index)}>Remove</Button>
</div>
))}
<Button onClick={() => append({ address: '' })}>Add</Button>
</div>
);
}
錯誤處理
lib/form 組件會自動處理欄位層級錯誤,但陣列層級錯誤需手動顯示:
// 欄位層級錯誤:lib/form 自動處理 ✅
<Input name="addresses.0.address" control={control} />
// 陣列層級錯誤(如「至少需要一個地址」):需手動處理
const getArrayError = (): string | undefined => {
const arrayError = errors?.[name];
if (arrayError && 'message' in arrayError) {
return arrayError.message as string;
}
return undefined;
};
{getArrayError() && (
<p className="text-error text-sm">{getArrayError()}</p>
)}
表單驗證規則
MUST use lib/form/validator, NEVER use Zod or Yup
// ❌ NEVER - 使用 Zod
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
const schema = z.object({
email: z.string().email(),
})
// ✅ ALWAYS - 使用 lib/form/validator
import { schema, validatorResolver } from '@appfuse/appfuse-web/form'
const customerSchema = schema.object({
name: schema.string().required(),
email: schema.string(), // 格式驗證交給後端
})
const { control } = useForm({
resolver: validatorResolver(customerSchema),
})
文檔參考
- 表單元件 - 表單組件規範
- lib/form/README.md - 表單組件原始碼 README
- lib/form/AGENTS.md - 表單開發規則(AI)
4. 工具函數使用規範
規則
MUST use lib/utils/*, NEVER use third-party utilities directly
// ❌ NEVER - 直接使用第三方套件
import axios from 'axios'
import dayjs from 'dayjs'
import { clsx } from 'clsx'
// ✅ ALWAYS - 使用 lib/utils
import { apiClient } from '@/services/api-client' // 基於 lib/utils/http
import { formatDate, toDateString } from '@appfuse/appfuse-web/utils'
import { cn } from '@appfuse/appfuse-web/utils'
工具對照表
| 需求 | 使用 | 路徑 | 禁止直接使用 |
|---|---|---|---|
| HTTP 請求 | apiClient | src/services/api-client | axios, fetch |
| 日期格式化 | formatDate, toDateString | lib/utils/time | dayjs, date-fns |
| Class 合併 | cn | lib/utils/cn | clsx, classnames |
| 國際化 | useTranslation, i18n.t | lib/utils/i18n | i18next 直接 |
| 日誌 | logger | lib/utils/logger | console.log |
| 環境變數 | environ | lib/utils/environ | import.meta.env |
| Cookie | cookie | lib/utils/cookie | js-cookie |
| 瀏覽器偵測 | browser | lib/utils/browser | - |
| 數字格式化 | formatNumber | lib/utils/numeral | - |
| 字串模板 | template | lib/utils/template | - |
環境變數(environ)
MUST use environ, NEVER use import.meta.env directly
// ❌ NEVER - 直接使用 import.meta.env
if (import.meta.env.DEV) { ... }
if (import.meta.env.MODE === 'development') { ... }
console.log(import.meta.env.VITE_API_URL)
// ✅ ALWAYS - 使用 environ
import { environ } from '@appfuse/appfuse-web/utils'
// 判斷開發環境
if (environ.app.stage === 'development') { ... }
// 判斷生產環境
if (environ.app.stage === 'production') { ... }
// 取得 API URL
console.log(environ.baseURL)
日誌記錄(logger)
MUST use logger, NEVER use console directly in production code
// ❌ NEVER - 直接使用 console
console.log('Debug info')
console.error('Error occurred:', error)
console.warn('Warning message')
// ✅ ALWAYS - 使用 logger
import { logger } from '@appfuse/appfuse-web/utils'
logger.debug('Debug info')
logger.error('Error occurred:', error)
logger.warn('Warning message')
例外情況:MSW mock 初始化日誌(src/mocks/)可使用 console
文檔參考
- 工具函數 - 工具函數規範
- lib/utils/README.md - 工具函數原始碼 README
- 各工具目錄下的
README.md- 詳細 API 文檔
5. Hooks 使用規範
規則
MUST check lib/hooks/* before creating custom hooks
// ❌ NEVER - 重複實作已有的 Hook
function useDebounce(value, delay) {
// ... 自行實作
}
// ✅ ALWAYS - 使用 lib/hooks
import { useDebounce, useTimeout } from '@appfuse/appfuse-web/hooks'
可用 Hooks
| Hook | 用途 |
|---|---|
useDebounce | 防抖處理 |
useTimeout | 延遲執行 |
useInterval | 定時執行 |
useLocalStorage | Local Storage 狀態 |
useMediaQuery | 響應式斷點 |
文檔參考
- Hooks - Hooks 規範
- lib/hooks/README.md - Hooks 原始碼 README
6. 訊息通知使用規範
規則
MUST use prompt.*() API, NEVER use native browser dialogs
// ❌ NEVER - 原生對話框
alert('Success!')
confirm('Delete this?')
window.alert('Error')
// ✅ ALWAYS - prompt API
import { prompt } from '@appfuse/appfuse-web/messaging'
prompt.success('Order created successfully')
prompt.error('Failed to save')
const answer = await prompt.confirm('Delete this item?', {
actions: ['delete', 'cancel']
})
API 對照表
| 需求 | 方法 | 顯示方式 |
|---|---|---|
| 成功通知 | prompt.success() | Toast(自動消失) |
| 資訊通知 | prompt.info() | Toast |
| 錯誤通知 | prompt.error() | 阻塞式對話框 |
| 警告通知 | prompt.warn() | 阻塞式對話框 |
| 確認對話框 | prompt.confirm() | 阻塞式對話框 |
Button 內建確認
// 危險操作(紅色警告)
<Button
warning={t('Are you sure to delete?')}
onClick={handleDelete}
>
{t('Delete')}
</Button>
// 一般確認(藍色資訊)
<Button
confirmation={t('Confirm this action?')}
onClick={handleAction}
>
{t('Execute')}
</Button>
文檔參考
例外情況
何時可以不使用 lib/?
-
lib/ 沒有提供對應功能
- 先評估是否應該擴展 lib/
- 如果時間緊迫,在 src/ 實作並標記
// TODO: migrate to lib/
-
特定業務邏輯
- 只有該 Applet 使用的私有組件
- 放在
src/applets/{applet}/components/
-
第三方整合
- 需要直接使用第三方 SDK(如 Google Maps)
- 包裝成 src/ 組件,隔離第三方依賴
已知的合理例外
| 情境 | 允許使用 | 原因 |
|---|---|---|
| 應用初始化階段 | import.meta.env.DEV | environ 尚未設定(main.tsx, mocks/browser.ts) |
| Redux 序列化 | parseISO from date-fns | lib/utils/time 尚未導出此功能 |
| 類型導入 | import type { AxiosError } | 僅導入類型定義,不影響執行 |
| 沙箱/示例 | 原生 HTML、date-fns、clsx | 教學示範用途(src/playground/) |
| MSW 初始化 | console.log | Mock 服務初始化日誌 |
檔案分類
| 目錄 | 規範適用性 |
|---|---|
src/applets/ | 完全適用 - 必須遵循所有規範 |
src/pages/ | 完全適用 - 必須遵循所有規範 |
src/services/ | 完全適用 - 必須遵循所有規範 |
src/playground/ | 部分適用 - 教學用途可有例外 |
src/mocks/ | 部分適用 - MSW 初始化可用 console |
*.example.tsx | 不適用 - 示例文檔 |
參考實作
以下是遵循本規範的參考實作:
| 模式 | 參考檔案 |
|---|---|
| 表單使用(lib/form) | src/applets/customer-applet/components/individual-customer-form.tsx |
| 陣列欄位(useFieldArray) | src/applets/customer-applet/components/address-list.tsx |
| 企業表單 | src/applets/customer-applet/components/corporate-customer-form.tsx |
| 聯絡人列表 | src/applets/customer-applet/components/contact-list.tsx |
| 資料表格 | src/applets/order-applet/order-finder.tsx |
| 訊息通知 | src/applets/order-applet/order-detail.tsx |
| 工具函數 | src/services/api-client.ts |
相關文件
最後更新: 2026-01-02