Blocker · 必修
Major · 強烈建議
Nit · 小毛病
Praise · 寫得好
src/components/UserProfile.tsx
[ 點此摺疊 / 展開 ]
@@ -12,18 +12,32 @@ export function UserProfile({ userId })
1212export function UserProfile({ userId }: Props) {
13 const [user, setUser] = useState<User | null>(null);
14 const [loading, setLoading] = useState(false);
15 const [error, setError] = useState<string | null>(null);
131 const [state, dispatch] = useReducer(profileReducer, initialState);
1614
1715 useEffect(() => {
18 setLoading(true);
162 dispatch({ type: 'FETCH_START' });
1917 fetch(`/api/users/${userId}`)
2018 .then(r => r.json())
21 .then(data => { setUser(data); setLoading(false); })
22 .catch(err => { setError(err.message); setLoading(false); });
193 .then(data => dispatch({ type: 'FETCH_OK', data }))
20 .catch(err => dispatch({ type: 'FETCH_ERR', error: err.message }));
2321 }, [userId]);
2422
234 if (state.status === 'loading') return <Skeleton />;
24 if (state.status === 'error') return <ErrorBox msg={state.error} />;
2525 return <ProfileCard user={state.user} />;
2626}
Major
L13
收斂三個 useState 改 useReducer 方向對。但
initialState 沒看到 export、確認一下是不是定義在同檔。建議放最上面、reader 第一眼看到「狀態長什麼樣」再往下看 logic。
Nit
L16
'FETCH_START' 這種 string literal 容易拼錯。後面有 8 個 dispatch、考慮抽成 const Actions = { ... } 或用 enum。
Blocker
L19
fetch 沒檢查 r.ok、API 回 500 也會走進 FETCH_OK、然後 data 是 error JSON。請加 if (!r.ok) throw new Error(...)。這是回歸測試也沒抓到的 bug、上次線上爆過一次。
Praise
L23
early return 三狀態這寫法很乾淨、比 ternary 巢狀好讀。沿用到其他 component。
src/components/profileReducer.ts
[ 點此摺疊 / 展開 ]
@@ -0,0 +1,28 @@ new file
15export type ProfileState =
2 | { status: 'idle' }
3 | { status: 'loading' }
4 | { status: 'ok'; user: User }
5 | { status: 'error'; error: string };
6
76export const initialState: ProfileState = { status: 'idle' };
8
9export function profileReducer(state: ProfileState, action: Action) {
10 switch (action.type) {
11 case 'FETCH_START': return { status: 'loading' };
127 case 'FETCH_OK': return { status: 'ok', user: action.data };
13 case 'FETCH_ERR': return { status: 'error', error: action.error };
14 default: return state;
15 }
16}
Blocker
L1
discriminated union 型別寫得好、但
'ok' 跟 component 裡 check 的 'loading' / 'error' 沒一致對齊。component L23 是 state.status === 'loading'、這裡是 'ok' 不是 'success'、容易看漏。建議統一用三字 verb:idle / loading / success / error。
Major
L7
initialState 是 'idle'、但 component useEffect 立刻 dispatch FETCH_START、所以 idle 永遠看不到。是設計選擇還是 dead state?如果 idle 永遠走不到、考慮砍掉、讓 type union 從三態起跑。
Nit
L12
action.data 沒 type annotation、TypeScript 可能 infer 成 any。建議在 Action type 裡明確標 { type: 'FETCH_OK'; data: User }。