第 156 章:TypeScript 集成
学习目标
- 掌握 React 组件的类型化
- 学会泛型组件
- 理解事件类型
- 解决常见类型问题
一、组件 Props 类型
1.1 interface
tsx
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled} className={`btn-${variant}`}>
{label}
</button>
);
}1.2 继承原生属性
tsx
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
}
function Input({ label, ...rest }: InputProps) {
return (
<label>
{label}
<input {...rest} />
</label>
);
}二、Children 类型
tsx
interface CardProps {
children: React.ReactNode; // 任意可渲染
// 或:
// children: React.ReactElement; // 单个元素
// children: (data: T) => ReactNode; // 函数
}三、事件类型
tsx
import {
ChangeEvent,
FormEvent,
MouseEvent,
KeyboardEvent,
FocusEvent,
} from 'react';
function Form() {
function onChange(e: ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
function onClick(e: MouseEvent<HTMLButtonElement>) {
console.log(e.currentTarget.disabled);
}
function onSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
}
function onKey(e: KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') { /* ... */ }
}
function onFocus(e: FocusEvent<HTMLInputElement>) { /* ... */ }
return <form onSubmit={onSubmit}><input onChange={onChange} onKeyDown={onKey} /><button onClick={onClick} /></form>;
}四、useState 类型
4.1 自动推断
tsx
const [count, setCount] = useState(0); // number
const [name, setName] = useState('Tom'); // string
const [list, setList] = useState([1, 2, 3]); // number[]4.2 显式标注
tsx
const [user, setUser] = useState<User | null>(null);
const [list, setList] = useState<User[]>([]);4.3 函数式更新
tsx
setList(prev => [...prev, newItem]); // prev 类型自动推断五、useRef 类型
tsx
// DOM 引用
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
// 可变值
const countRef = useRef<number>(0);
countRef.current = 1;六、useReducer 类型
tsx
interface State { count: number; }
type Action = { type: 'inc' } | { type: 'dec' } | { type: 'set'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'inc': return { count: state.count + 1 };
case 'dec': return { count: state.count - 1 };
case 'set': return { count: action.payload };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });七、Context 类型
tsx
import { createContext, useContext } from 'react';
interface Theme {
mode: 'light' | 'dark';
toggle: () => void;
}
const ThemeContext = createContext<Theme | null>(null);
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('必须在 Provider 内');
return ctx;
}八、泛型组件
8.1 泛型列表
tsx
interface ListProps<T> {
items: T[];
render: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string | number;
}
function List<T>({ items, render, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{render(item)}</li>
))}
</ul>
);
}
// 使用
<List<User>
items={users}
render={u => <span>{u.name}</span>}
keyExtractor={u => u.id}
/>8.2 泛型 Hook
tsx
function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(initial);
// ...
return [value, setValue] as const;
}
// 使用(自动推断)
const [count, setCount] = useLocalStorage('count', 0);
const [user, setUser] = useLocalStorage<User | null>('user', null);九、forwardRef 类型(React 19 前)
tsx
import { forwardRef, Ref } from 'react';
interface InputProps {
label: string;
}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ label }, ref) => (
<>
<label>{label}</label>
<input ref={ref} />
</>
)
);
// 使用
const ref = useRef<HTMLInputElement>(null);
<Input label="姓名" ref={ref} />React 19:
ref直接作为 prop,不再需要forwardRef。
十、常见类型问题
10.1 event.currentTarget 可能为 null
tsx
function handler(e: MouseEvent<HTMLButtonElement>) {
// ✅ 显式判断
if (e.currentTarget) {
e.currentTarget.disabled = true;
}
}10.2 数组 map 中的 key
tsx
// ❌ key 类型问题
{items.map((item: any) => <li key={item.id}>...</li>)}
// ✅ 类型化
interface Item { id: number; name: string; }
{items.map((item: Item) => <li key={item.id}>...</li>)}10.3 第三方库类型缺失
bash
pnpm add @types/xxx
# 或
pnpm add -D @types/react @types/react-dom十一、组件库类型示例
tsx
// 引入第三方库类型
import { Button, Modal } from 'antd';
// 自定义包装
type AntButtonProps = React.ComponentProps<typeof Button> & {
customProp?: string;
};
function MyButton(props: AntButtonProps) {
return <Button {...props} />;
}十二、本章小结
| 概念 | 关键 |
|---|---|
| Props | interface 或 type |
| 事件 | ChangeEvent<HTMLInputElement> 等 |
| useState | 推断或显式标注 |
| useRef | <HTMLInputElement | null> |
| 泛型组件 | <T> 接收任意类型 |
| 常见坑 | currentTarget null、key 类型 |
动手练习
- 给现有组件加 TypeScript 类型
- 写一个泛型
Select<T>组件 - 写一个泛型
useApi<T>Hook - 修复一个未类型化的项目
推荐阅读
下一章:第 157 章:Tailwind CSS 样式 →