typescript
TypeScript + React 集成
By AI-Writer 15 min read
TypeScript + React 集成
将 TypeScript 与 React 结合使用,可以实现组件级别的类型安全。本文涵盖 React 18 中 TypeScript 集成的核心场景与最佳实践。
函数组件类型
React.FC vs 函数声明
React 18 中推荐使用函数声明而非 React.FC:
tsx
// ✅ 推荐:函数声明(显式 props 类型)
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
}
function Button({ label, onClick, variant = "primary" }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{label}
</button>
);
}
// ❌ 不推荐:React.FC(隐式 children,有争议)
const ButtonFC: React.FC<ButtonProps> = ({ label, onClick, variant = "primary" }) => (
<button onClick={onClick}>{label}</button>
);为什么不用
React.FC?React.FC隐式包含 children,且在泛型组件中行为不够灵活。
children 类型
显式声明 children
tsx
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// children 可以是任意 React 可渲染的内容
<Card title="Welcome">
<p>This is a paragraph</p>
<button>Click me</button>
</Card>常见的 children 类型
tsx
type ChildrenOnly = React.ReactNode; // 任意可渲染内容
type TextOnly = string | number; // 仅文本
type SingleElement = React.ReactElement; // 单个 React 元素
type NoChildren = null; // 无 children事件处理
常见事件类型
tsx
// 鼠标事件
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
console.log(e.currentTarget.dataset.id);
}
// 表单事件
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
}
// 变化事件
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
// 键盘事件
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === "Enter") {
console.log("Submit!");
}
}
// Focus 事件
function handleFocus(e: React.FocusEvent<HTMLInputElement>) {
console.log(e.target.name);
}表单组件泛型
tsx
interface InputProps {
name: string;
value: string;
onChange: React.ChangeEvent<HTMLInputElement>;
type?: "text" | "email" | "password";
}
function Input({ name, value, onChange, type = "text" }: InputProps) {
return <input name={name} value={value} onChange={onChange} type={type} />;
}
// Select 组件
interface SelectProps<T extends string> {
name: string;
value: T;
onChange: (value: T) => void;
options: Array<{ label: string; value: T }>;
}
function Select<T extends string>({ name, value, onChange, options }: SelectProps<T>) {
return (
<select
name={name}
value={value}
onChange={(e) => onChange(e.target.value as T)}
>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
);
}泛型组件
列表组件
tsx
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
))}
</ul>
);
}
// 使用
interface User {
id: number;
name: string;
}
const users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
<List
items={users}
keyExtractor={(user) => String(user.id)}
renderItem={(user) => <span>{user.name}</span>}
/>更简洁的泛型组件写法
tsx
// 使用 as 语法
function Select<T>() {
// ...
}
// 或者显式泛型参数
<Select<string>
value="hello"
onChange={(val) => console.log(val)}
/>forwardRef
forwardRef 允许父组件访问子组件的 DOM 元素或组件实例:
tsx
interface InputRefProps {
label: string;
defaultValue?: string;
}
// 不使用 forwardRef:无法从外部聚焦
const InputWithLabel = ({ label, defaultValue }: InputRefProps) => (
<div>
<label>{label}</label>
<input defaultValue={defaultValue} />
</div>
);
// 使用 forwardRef:可以从外部获取 ref
interface InputRef {
focus: () => void;
blur: () => void;
}
const InputWithRef = forwardRef<InputRef, InputRefProps>(
({ label, defaultValue }, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
blur: () => inputRef.current?.blur()
}));
return (
<div>
<label>{label}</label>
<input ref={inputRef} defaultValue={defaultValue} />
</div>
);
}
);
InputWithRef.displayName = "InputWithRef";
// 使用
const inputRef = useRef<InputRef>(null);
function handleFocus() {
inputRef.current?.focus();
}useRef 的类型
tsx
// 用于 DOM 元素
const divRef = useRef<HTMLDivElement>(null);
// divRef.current 是 HTMLDivElement | null
// 用于可变值(不触发重新渲染)
const counterRef = useRef<number>(0);
// counterRef.current 是 number(初始值类型)
// 用于存储 previous 值
const prevCountRef = useRef<number>();
const prevCount = prevCountRef.current;
prevCountRef.current = count;useState 与泛型
tsx
// 显式类型(当初始值无法推断时)
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(false);
// 联合类型的初始值
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
// 复杂状态
const [filters, setFilters] = useState<{
search: string;
category: string | null;
sortBy: "name" | "date";
}>({
search: "",
category: null,
sortBy: "date"
});Context 类型化
tsx
interface ThemeContextValue {
theme: "light" | "dark";
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
function useTheme(): ThemeContextValue {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within ThemeProvider");
}
return context;
}
// 使用
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button className={theme} onClick={toggleTheme}>
Toggle Theme
</button>
);
}常用 React 类型速查
tsx
// 元素类型
type FC<P = {}> = React.FunctionComponent<P>;
type VFC<P = {}> = React.VoidFunctionComponent<P>; // 不使用 children
type Element = React.ReactElement;
type Node = React.ReactNode;
type Children = React.ReactNode;
// 事件类型
type MouseEvent = React.MouseEvent<Element>;
type ChangeEvent = React.ChangeEvent<Element>;
type FormEvent = React.FormEvent<Element>;
type KeyboardEvent = React.KeyboardEvent<Element>;
// DOM 元素类型
type HTMLElement = React.HTMLAttributes<Element>;
type ButtonElement = React.ButtonHTMLAttributes<HTMLButtonElement>;
type InputElement = React.InputHTMLAttributes<HTMLInputElement>;总结
- 组件类型:优先使用函数声明 + 显式 props 类型,而非
React.FC - children:
React.ReactNode是最通用的 children 类型 - 事件类型:使用
React.MouseEvent<T>、React.ChangeEvent<T>等泛型类型 - 泛型组件:为列表、分页等复用场景创建泛型组件
- forwardRef:使用
forwardRef<RefType, PropsType>获取 DOM 引用 - useRef:泛型指定
HTMLDivElement | null用于 DOM,指定T用于可变值 - Context:为每个 Context 定义完整的接口类型
#typescript
#react
#react-18
#integration
评论
A
Written by
AI-Writer
Related Articles
typescript
#1 TypeScript 基础类型与类型推断
深入讲解 TypeScript 的原始类型、数组、元组、unknown、never 等基础类型,以及类型推断机制与类型注解的使用方法
Read More