在基于 TypeScript 构建 React 组件、编写 Hook 类型声明、处理事件,或者提及 React TypeScript、React 19、服务端组件(Server Components)时使用本 Skill。涵盖适用于 React 18-19 的类型安全最佳实践,包括泛型组件、精准事件类型标注以及路由集成(TanStack Router、React Router)。
React TypeScript
类型安全的 React = 编译期保障 = 敢重构、不踩坑。
<when_to_use>
- 构建带类型定义的 React 组件
- 实现泛型组件
- 为事件处理函数、表单、Ref 标注类型
- 使用 React 19 新特性(Actions、Server Components、use())
- 路由集成(TanStack Router、React Router)
- 编写具备精准类型推导的自定义 Hook
不适用于:非 React 的 TypeScript 项目、原生 JS 版 React
</when_to_use>
<react_19_changes>
React 19 的破坏性变更需要进行迁移。核心写法如下:
ref 作为普通 prop - forwardRef 已废弃:
// 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); // 挂起(Suspend)直到 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; // 单个 React 元素
render: (data: T) => React.ReactNode; // Render prop 模式
};
可辨识联合(Discriminated unions) 声明变体 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>
使用精确的事件类型,以获得准确的目标对象类型推导:
// 鼠标事件
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.currentTarget.disabled = true;
}
// 表单事件
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
}
// 输入框事件
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
// 键盘事件
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') e.currentTarget.blur();
}
查阅 event-handlers.md 获取焦点、拖拽、剪贴板、触摸及滚轮等事件的类型声明。
</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 - 元组返回值使用 as const 进行收窄:
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue(v => !v);
return [value, toggle] as const;
}
useContext - null 守卫模式:
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 Prop 自定义渲染逻辑:
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)运行在服务端,可以直接声明为异步函数。
异步数据获取:
export default async function UserPage({ params }: { params: { id: string } }) {
const user = await fetchUser(params.id);
return <div>{user.name}</div>;
}
Server Actions - 使用 'use server' 处理数据变更:
'use server';
export async function updateUser(userId: string, formData: FormData) {
await db.user.update({ where: { id: userId }, data: { ... } });
revalidatePath(`/users/${userId}`);
}
客户端组件配合 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:
// 服务端:无需 await,直接向下传递 Promise
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 了解并行请求、流式传输(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 扩展原生 HTML 元素
- 使用可辨识联合(Discriminated unions)定义变体 Props
- 元组返回值务必标注
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 组件
- 在传递给
use()之前在服务端提前 await Promise
</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 类型化路由、搜索参数与导航
- react-router.md - React Router v7 Loader、Action、类型生成与表单处理
</references>






