跳至主要内容

Framework Usage 規範

目標讀者: 前端開發者、AI 助手

本文檔定義參考實作(src/)應如何使用框架(lib/)提供的組件與工具。 這些規範確保應用程式具有一致的程式碼品質與可維護性。


目錄

  1. 設計原則
  2. UI 組件使用規範
  3. 表單組件使用規範
  4. 工具函數使用規範
  5. Hooks 使用規範
  6. 訊息通知使用規範
  7. 例外情況

1. 設計原則

核心原則

優先使用框架(lib/)提供的組件與工具,避免直接使用第三方套件

這確保:

  1. 一致性 - 所有頁面使用相同的 UI 語言
  2. 主題支援 - 自動支援 DaisyUI 30+ 主題
  3. 國際化 - 內建 i18n 支援
  4. 可維護性 - 統一的 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/* 的整合版本。

文檔參考


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),
})

文檔參考


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 請求apiClientsrc/services/api-clientaxios, fetch
日期格式化formatDate, toDateStringlib/utils/timedayjs, date-fns
Class 合併cnlib/utils/cnclsx, classnames
國際化useTranslation, i18n.tlib/utils/i18ni18next 直接
日誌loggerlib/utils/loggerconsole.log
環境變數environlib/utils/environimport.meta.env
Cookiecookielib/utils/cookiejs-cookie
瀏覽器偵測browserlib/utils/browser-
數字格式化formatNumberlib/utils/numeral-
字串模板templatelib/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

文檔參考


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定時執行
useLocalStorageLocal Storage 狀態
useMediaQuery響應式斷點

文檔參考


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/?

  1. lib/ 沒有提供對應功能

    • 先評估是否應該擴展 lib/
    • 如果時間緊迫,在 src/ 實作並標記 // TODO: migrate to lib/
  2. 特定業務邏輯

    • 只有該 Applet 使用的私有組件
    • 放在 src/applets/{applet}/components/
  3. 第三方整合

    • 需要直接使用第三方 SDK(如 Google Maps)
    • 包裝成 src/ 組件,隔離第三方依賴

已知的合理例外

情境允許使用原因
應用初始化階段import.meta.env.DEVenviron 尚未設定(main.tsx, mocks/browser.ts)
Redux 序列化parseISO from date-fnslib/utils/time 尚未導出此功能
類型導入import type { AxiosError }僅導入類型定義,不影響執行
沙箱/示例原生 HTML、date-fns、clsx教學示範用途(src/playground/
MSW 初始化console.logMock 服務初始化日誌

檔案分類

目錄規範適用性
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