當使用 TypeScript 建立 React 元件、定義 Hook 型別、處理事件,或提及 React TypeScript、React 19、Server Components 時,應使用此 Skill。涵蓋 React 18-19 的型別安全模式,包含泛型元件、精準的事件型別定義以及路由整合(TanStack Router、React Router)。
React TypeScript
具備型別安全的 React = 編譯時期的品質保證 = 更有自信地進行程式碼重構。
<when_to_use>
- 建立具備型別定義的 React 元件
- 實作泛型元件(Generic Components)
- 定義事件處理函式(Event Handlers)、表單與 Ref 的型別
- 使用 React 19 新功能(Actions、Server Components、use())
- 路由整合(TanStack Router、React Router)
- 撰寫具備完整型別定義的自訂 Hook
不適用於:非 React 的 TypeScript 專案、純 JS 的 React 專案
</when_to_use>
<react_19_changes>
React 19 的重大變更(Breaking Changes)需要進行遷移。核心模式如下:
ref 作為 Prop 傳遞 - forwardRef 已廢棄(Deprecated):
// React 19 - 將 ref 作為一般 Prop 傳遞
type ButtonProps = {
ref?: React.Ref<HTMLButtonElement>;
} & React.ComponentPropsWithoutRef<'button'>;
function Button({ ref, children, ...props }: ButtonProps) {
return <button ref={ref} {...props}>{children}</button>;
}
useActionState - 取代 useFormState:
import { useActionState } from 'react';
type FormState = { errors?: string[]; success?: boolean };
function Form() {
const [state, formAction, isPending] = useActionState(submitAction, {});
return <form action={formAction}>...</form>;
}
use() - 解包 Promise / Context:
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // 暫停渲染 (Suspends) 直到 Promise 完成
return <div>{user.name}</div>;
}
請參閱 react-19-patterns.md 瞭解 useOptimistic、useTransition 與遷移檢核表。
</react_19_changes>
<component_patterns>
Props - 擴充原生 HTML 元素:
type ButtonProps = {
variant: 'primary' | 'secondary';
} & React.ComponentPropsWithoutRef<'button'>;
function Button({ variant, children, ...props }: ButtonProps) {
return <button className={variant} {...props}>{children}</button>;
}
Children 型別定義:
type Props = {
children: React.ReactNode; // 任何可渲染的內容
icon: React.ReactElement; // 單一元素
render: (data: T) => React.ReactNode; // Render prop
};
使用**可辨識聯集(Discriminated unions)**定義 Variant Props:
type ButtonProps =
| { variant: 'link'; href: string }
| { variant: 'button'; onClick: () => void };
function Button(props: ButtonProps) {
if (props.variant === 'link') {
return <a href={props.href}>Link</a>;
}
return <button onClick={props.onClick}>Button</button>;
}
</component_patterns>
<event_handlers>
使用具體的事件型別以精準推導 target 型別:
// 滑鼠事件 (Mouse)
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.currentTarget.disabled = true;
}
// 表單事件 (Form)
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
}
// 輸入事件 (Input)
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
// 鍵盤事件 (Keyboard)
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') e.currentTarget.blur();
}
請參閱 event-handlers.md 瞭解焦點 (Focus)、拖曳 (Drag)、剪貼簿 (Clipboard)、觸控 (Touch) 及滾輪 (Wheel) 事件。
</event_handlers>
<hooks_typing>
useState - 為聯集型別或 null 進行明確宣告:
const [user, setUser] = useState<User | null>(null);
const [status, setStatus] = useState<'idle' | 'loading'>('idle');
useRef - DOM 元素預設給 null,可變數值則直接傳入初始值:
const inputRef = useRef<HTMLInputElement>(null); // DOM 元素 - 使用 ?.
const countRef = useRef<number>(0); // 可變數值 - 直接存取
useReducer - 使用可辨識聯集定義 Action:
type Action =
| { type: 'increment' }
| { type: 'set'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'set': return { ...state, count: action.payload };
default: return state;
}
}
自訂 Hook(Custom hooks) - 回傳 Tuple 時加上 as const:
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue(v => !v);
return [value, toggle] as const;
}
useContext - 空值防禦模式(Null guard pattern):
const UserContext = createContext<User | null>(null);
function useUser() {
const user = useContext(UserContext);
if (!user) throw new Error('useUser 必須在 UserProvider 內部使用');
return user;
}
請參閱 hooks.md 瞭解 useCallback、useMemo、useImperativeHandle 與 useSyncExternalStore。
</hooks_typing>
<generic_components>
泛型元件能直接從 Props 自動推導型別 - 在呼叫端無需手動標註型別。
通用模式 - 使用 keyof T 作為欄位 Key,使用 Render props 進行自訂渲染:
type Column<T> = {
key: keyof T;
header: string;
render?: (value: T[keyof T], item: T) => React.ReactNode;
};
type TableProps<T> = {
data: T[];
columns: Column<T>[];
keyExtractor: (item: T) => string | number;
};
function Table<T>({ data, columns, keyExtractor }: TableProps<T>) {
return (
<table>
<thead>
<tr>{columns.map(col => <th key={String(col.key)}>{col.header}</th>)}</tr>
</thead>
<tbody>
{data.map(item => (
<tr key={keyExtractor(item)}>
{columns.map(col => (
<td key={String(col.key)}>
{col.render ? col.render(item[col.key], item) : String(item[col.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
受限泛型(Constrained generics) - 用於確保必要的屬性存在:
type HasId = { id: string | number };
function List<T extends HasId>({ items }: { items: T[] }) {
return <ul>{items.map(item => <li key={item.id}>...</li>)}</ul>;
}
請參閱 generic-components.md 瞭解 Select、List、Modal 與 FormField 的設計模式。
</generic_components>
<server_components>
React 19 Server Components 於伺服器端執行,支援非同步(async)。
非同步資料取得(Async data fetching):
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await fetchUser(params.id);
return <div>{user.name}</div>;
}
Server Actions - 使用 'use server' 進行資料異動(Mutations):
'use server';
export async function updateUser(userId: string, formData: FormData) {
await db.user.update({ where: { id: userId }, data: { ... } });
revalidatePath(`/users/${userId}`);
}
Client + Server Action 結合:
'use client';
import { useActionState } from 'react';
import { updateUser } from '@/actions/user';
function UserForm({ userId }: { userId: string }) {
const [state, formAction, isPending] = useActionState(
(prev, formData) => updateUser(userId, formData), {}
);
return <form action={formAction}>...</form>;
}
使用 use() 傳遞 Promise:
// 伺服器端:直接傳遞 Promise 而不上鎖 (不使用 await)
async function Page() {
const userPromise = fetchUser('123');
return <UserProfile userPromise={userPromise} />;
}
// 客戶端:使用 use() 解包 Promise
'use client';
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
請參閱 server-components.md 瞭解平行取得(Parallel fetching)、串流(Streaming)與錯誤邊界(Error boundaries)。
</server_components>
<routing>
TanStack Router 與 React Router v7 皆提供具備型別安全的路由解決方案。
TanStack Router - 搭配 Zod 驗證實現編譯時期的型別安全:
import { createRoute } from '@tanstack/react-router';
import { z } from 'zod';
const userRoute = createRoute({
path: '/users/$userId',
component: UserPage,
loader: async ({ params }) => ({ user: await fetchUser(params.userId) }),
validateSearch: z.object({
tab: z.enum(['profile', 'settings']).optional(),
page: z.number().int().positive().default(1),
}),
});
function UserPage() {
const { user } = useLoaderData({ from: userRoute.id });
const { tab, page } = useSearch({ from: userRoute.id });
const { userId } = useParams({ from: userRoute.id });
}
React Router v7 - 在 Framework Mode 下自動生成型別:
import type { Route } from "./+types/user";
export async function loader({ params }: Route.LoaderArgs) {
return { user: await fetchUser(params.userId) };
}
export default function UserPage({ loaderData }: Route.ComponentProps) {
const { user } = loaderData; // 自動從 loader 推導型別
return <h1>{user.name}</h1>;
}
請參閱 tanstack-router.md 瞭解 TanStack 模式,以及 react-router.md 瞭解 React Router 模式。
</routing>
<rules>
務必遵從(ALWAYS):
- 使用具體的事件型別(MouseEvent、ChangeEvent 等)
- 為聯集型別或 null 進行明確的 useState 宣告
- 使用 ComponentPropsWithoutRef 來擴充原生元素
- 使用可辨識聯集(Discriminated unions)定義 Variant Props
- 回傳 Tuple 時加上 as const
- 在 React 19 中將 ref 作為 Prop 傳遞(不再使用 forwardRef)
- 使用 useActionState 處理表單 Action
- 採用型別安全的路由模式(請參閱 routing 章節)
絕對避免(NEVER):
- 在事件處理函式中使用 any
- 在 children 使用 JSX.Element(請改用 ReactNode)
- 在 React 19+ 中使用 forwardRef
- 使用 useFormState(已廢棄)
- 遺漏 DOM ref 的 null 處理
- 在同一個檔案中混用 Server/Client 元件
- 傳遞 Promise 給 use() 時提前 await
</rules>
<references>
- hooks.md - useState、useRef、useReducer、useContext 及自訂 Hook
- event-handlers.md - 所有事件型別與通用處理函式
- react-19-patterns.md - useActionState、use()、useOptimistic 與遷移指南
- generic-components.md - Table、Select、List、Modal 等設計模式
- server-components.md - 非同步元件、Server Actions 與串流
- tanstack-router.md - TanStack Router 具型別路由、搜尋參數 (search params) 及導覽
- react-router.md - React Router v7 loaders、actions、型別生成與表單
</references>






