react-design
SKILL.md
資料夾路徑說明
注意:
{ModuleName}為模組/單元名稱變數,請依實際功能替換(例如:DataExchanges、UserManagement、Dashboard 等)
src/app/{ModuleName}/
├── components/ # 組件層:UI 組件與容器組件
├── hooks/ # 自訂 Hook:狀態邏輯與業務邏輯封裝
├── services/ # API 服務層:後端 API 呼叫封裝
├── context/ # Context 層:跨組件共享狀態(謹慎使用)
├── utils/ # 工具函數:純函數、格式化、驗證等
├── types/ # TypeScript 型別定義(選用)
各層職責說明
| 資料夾 | 職責 | 範例 |
|---|---|---|
components/ |
純 UI 渲染、事件綁定 | UserList.tsx, FormDialog.tsx |
hooks/ |
狀態管理、副作用處理、業務邏輯 | useUserList.ts, useFormValidation.ts |
services/ |
API 請求封裝、資料轉換 | userService.ts, authService.ts |
context/ |
跨層級狀態共享(僅在必要時使用) | UserContext.tsx |
utils/ |
純函數、無副作用的工具 | formatDate.ts, validators.ts |
架構設計原則
1. Context 使用原則
- ❌ 避免:僅用 Context 包裝單一 Hook(增加不必要的複雜度)
- ✅ 適用場景:
- 多個不相關組件需要共享相同狀態
- 深層嵌套組件需要存取狀態(避免 prop drilling)
- 全域性狀態(如:主題、語系、使用者認證)
// ❌ 不推薦:Context 只是簡單包裝 Hook
const MyContext = createContext(null);
const MyProvider = ({ children }) => {
const hookValue = useMyHook(); // 只是轉發 hook
return <MyContext.Provider value={hookValue}>{children}</MyContext.Provider>;
};
// ✅ 推薦:直接使用 Hook
const MyComponent = () => {
const { data, actions } = useMyHook();
return <div>{/* 使用 data 和 actions */}</div>;
};
2. Hook 設計原則
單一職責原則(SRP)
每個 Hook 只負責一個明確的功能領域:
// ❌ 不推薦:一個 Hook 管理多個不相關狀態
const useEverything = () => {
const [users, setUsers] = useState([]);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
// ... 混雜多種邏輯
};
// ✅ 推薦:拆分為獨立的 Hook
const useUsers = () => { /* 只管理使用者相關狀態 */ };
const useTheme = () => { /* 只管理主題相關狀態 */ };
const useNotifications = () => { /* 只管理通知相關狀態 */ };
降低 Hook 間耦合
- 避免 Hook A 直接呼叫 Hook B
- 使用組合模式:在組件層組合多個 Hook
- 共用邏輯提取到
utils/純函數
// ❌ 不推薦:Hook 間強耦合
const useUserActions = () => {
const { data } = useUserList(); // 直接依賴另一個 Hook
// ...
};
// ✅ 推薦:組件層組合
const UserPage = () => {
const { users } = useUserList();
const { updateUser } = useUserActions();
// 在組件層協調兩個 Hook
const handleUpdate = (id, data) => updateUser(id, data);
};
3. 服務層設計原則
// services/userService.ts
// 封裝 API 呼叫,統一錯誤處理與資料轉換
export const userService = {
// 取得使用者列表
getUsers: async (params?: QueryParams): Promise<User[]> => {
const response = await api.get('/users', { params });
return response.data;
},
// 更新使用者資料
updateUser: async (id: string, data: UpdateUserDto): Promise<User> => {
const response = await api.put(`/users/${id}`, data);
return response.data;
},
};
4. 組件設計原則
容器組件 vs 展示組件
// 容器組件:負責邏輯與狀態
const UserListContainer = () => {
const { users, loading, error } = useUserList();
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <UserList users={users} />;
};
// 展示組件:純 UI 渲染
const UserList = ({ users }: { users: User[] }) => (
<ul>
{users.map(user => <UserItem key={user.id} user={user} />)}
</ul>
);
架構目標
- 檢查 Context 必要性:若只是單純包裝 Hook,應考慮移除
- 評估 Hook 使用時機:簡單狀態可用
useState,複雜邏輯再抽取 Hook - Hook 單一職責:避免單一 Hook 承擔過多責任
- 降低 Hook 耦合:Hook 間避免直接依賴,改由組件層組合
- 簡化 Context 實作:避免 Context 僅作為 Hook 的轉發層
- 提升可維護性:降低耦合性、簡化狀態管理、提高代碼可讀性
- 狀態分離:不相關的狀態不應放在同一個 Hook 管理
- 避免 useEffect 內同步呼叫 setState:確保狀態更新不會導致無限重渲染,非必要不要在 useEffect 內直接呼叫 setState(Calling setState synchronously within an effect can trigger cascading renders)
第 8 點補充說明
在 useEffect 內同步呼叫 setState 會觸發連鎖渲染(cascading renders),可能導致:
- 效能問題(不必要的多次重渲染)
- 無限迴圈(當 state 作為 dependency 時)
- React 開發模式警告
// ❌ 不推薦:useEffect 內直接呼叫 setState
useEffect(() => {
setCount(count + 1); // 觸發連鎖渲染
}, [someValue]);
// ❌ 危險:可能導致無限迴圈
useEffect(() => {
setData(transformData(data)); // data 變更 → 觸發 effect → 又變更 data
}, [data]);
// ✅ 推薦:在事件處理函式中更新狀態
const handleClick = () => {
setCount(prev => prev + 1);
};
// ✅ 推薦:使用 useMemo 計算衍生值,避免額外 state
const transformedData = useMemo(() => transformData(data), [data]);
何時可以在 useEffect 內使用 setState:
- 非同步操作完成後(如 API 回應)
- 訂閱外部資料源時
- 需要根據 props 初始化一次性狀態
重構注意事項
- ✅ 保證重構後原有功能不受影響
- ✅ 逐步重構,每次完成一個部分就進行測試
- ✅ 保持現有的 API 介面和用戶體驗不變
- ✅ 不需要更新單測檔
程式碼註解規範
需要註解的情況
- ✅ 邏輯互動的程式碼(說明 為什麼 這樣做)
- ✅ 複雜的業務邏輯
- ✅ 非顯而易見的實作決策
不需要註解的情況
- ❌ UI 樣式相關程式碼
- ❌ 資料結構/型別定義
- ❌ 未變更的程式碼
- ❌ 顯而易見的程式碼
// ✅ 好的註解:說明why
// 使用 debounce 避免使用者快速輸入時頻繁觸發 API 請求
const debouncedSearch = useMemo(
() => debounce(handleSearch, 300),
[handleSearch]
);
// ❌ 不好的註解:只說明what(程式碼本身已經說明)
// 設定 loading 為 true
setLoading(true);
Weekly Installs
1
Repository
ting-s515/skillsFirst Seen
11 days ago
Security Audits
Installed on
mcpjam1
claude-code1
replit1
junie1
windsurf1
zencoder1