太棒了!👏 你已经掌握了 React 的核心架构,现在是时候让代码更健壮、可维护、专业了!
—— 类型安全的现代开发方式
💡 为什么大厂项目几乎都用 TypeScript?
因为它能在编码时就发现错误,提升代码质量、团队协作效率和重构信心。
一、TypeScript 能为 React 带来什么?✨
| 优势 | 说明 |
|---|---|
| 🛡️ 类型检查 | 提前发现 props 传错、方法调用错误等问题 |
| 💡 智能提示 | VSCode 自动补全组件属性、状态结构 |
| 🧩 文档即代码 | 类型定义本身就是 API 文档 |
| 🔧 安全重构 | 改名/删字段时,编辑器会帮你找到所有引用处 |
| 👥 团队协作 | 统一接口规范,减少沟通成本 |
✅ 现实场景:
- 不再因为拼错
userName写成username导致白屏 - 看到组件就知道它需要哪些 props,怎么用
二、基础配置(快速上手)
1. 创建支持 TS 的 React 项目
# 使用 Vite(推荐)
npm create vite@latest my-app -- --template react-ts
# 或使用 Create React App(旧)
npx create-react-app my-app --template typescript
文件后缀变为:.tsx(含 JSX) 和 .ts(纯逻辑)
三、给函数式组件添加类型 ✅
✅ 方式 1:使用 interface 定义 Props 类型
// components/UserCard.tsx
import { FC } from 'react';
interface User {
name: string;
age: number;
email?: string; // 可选属性
}
interface UserCardProps {
user: User;
onRemove: (id: number) => void;
userId: number;
}
const UserCard: FC<UserCardProps> = ({ user, onRemove, userId }) => {
return (
<div>
<h3>{user.name}</h3>
<p>年龄:{user.age}</p>
{user.email && <p>邮箱:{user.email}</p>}
<button onClick={() => onRemove(userId)}>删除</button>
</div>
);
};
export default UserCard;
🔍 FC 是 FunctionComponent 的缩写,官方推荐但非必须。
现在更流行直接写函数类型:
const UserCard = ({ user, onRemove }: UserCardProps) => { ... }
✅ 方式 2:不使用 FC(现代推荐写法)
// 推荐:更简洁,避免 children 默认可选的问题
const UserCard = ({ user, onRemove }: UserCardProps) => {
return (
<div>
<h3>{user.name}</h3>
<button onClick={() => onRemove(user.id)}>删除</button>
</div>
);
};
四、常见类型的实战应用 💡
1. 处理事件类型
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget);
};
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
<button onClick={handleClick}>点我</button>
<input onChange={handleInput} />
| 常见事件类型 | 对应元素 |
|---|---|
React.ChangeEvent<HTMLInputElement> | <input> |
React.MouseEvent<HTMLButtonElement> | <button> |
React.FormEvent<HTMLFormElement> | <form onSubmit> |
2. State 类型推断
const [users, setUsers] = useState<User[]>([]); // 用户列表
const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');
// 字符串字面量联合类型,只能是这三个值之一
3. 使用泛型的自定义 Hook
// hooks/useLocalStorage.ts
import { useState, useEffect } from 'react';
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
const [value, setValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
}, [key, value]);
return [value, setValue];
}
export default useLocalStorage;
✅ 使用方式:
const [darkMode, setDarkMode] = useLocalStorage<boolean>('theme', false);
const [name, setName] = useLocalStorage<string>('name', '');
✨ 泛型让 Hook 更通用、类型更安全!
五、高级技巧与最佳实践 ✅
✅ 1. 类型守卫(Type Guards)
处理可能为空的情况:
function getUserAge(user: User | null): string {
if (!user) return '未知';
return `${user.age} 岁`;
}
或使用断言(谨慎):
const age = user!.age; // 明确告诉 TS:我保证 user 不为 null
✅ 2. Partial / Omit / Pick 工具类型
// 假设有完整用户类型
interface FullUser {
id: number;
name: string;
email: string;
password: string;
}
// 注册表单不需要 id 和 password
type RegisterForm = Omit<FullUser, 'id' | 'password'>;
// 编辑用户时部分字段可选
type UpdateUser = Partial<FullUser>;
✅ 3. 给 Context 添加类型
// context/AuthContext.tsx
import { createContext } from 'react';
interface AuthState {
user: { name: string } | null;
isLoggedIn: boolean;
}
interface AuthActions {
login: (username: string) => void;
logout: () => void;
}
type AuthContextType = AuthState & AuthActions;
export const AuthContext = createContext<AuthContextType | undefined>(undefined);
然后在 useContext 中使用:
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth 必须在 AuthProvider 内使用');
return context;
六、避坑指南 ⚠️
❌ 错误 1:滥用 any
const data: any = await fetch('/api').then(res => res.json());
// ❌ 失去了类型保护意义
✅ 正确做法:定义接口
interface ApiResponse {
users: { id: number; name: string }[];
total: number;
}
const data = await fetch('/api').then(res => res.json()) as ApiResponse;
更好:使用 Zod 或 io-ts 做运行时校验(进阶)
❌ 错误 2:忽略 children 类型
interface ModalProps {
isOpen: boolean;
// 如果你希望支持 children
children: React.ReactNode;
}
React.ReactNode 是最安全的选择,它可以是:
string,number,JSX.Element,null,undefined, 数组等
七、实战练习 🏋️♂️
✅ 练习 1:类型化 TodoList
将之前的 TodoList 用 TypeScript 重写:
- 定义
Todo接口:
interface Todo {
id: number;
text: string;
completed: boolean;
}
- 为
TodoItem组件添加 props 类型 - 为
onToggle和onDelete回调添加函数类型
✅ 练习 2:构建类型安全的 useFetch
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: string | null;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then(res => {
if (!res.ok) throw new Error(`错误: ${res.status}`);
return res.json();
})
.then(data => setData(data as T))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
使用:
interface User {
id: number;
name: string;
}
const { data: users, loading } = useFetch<User[]>('/api/users');
✅ 总结:React + TypeScript 核心要点
| 概念 | 要点 | |
|---|---|---|
| 文件扩展名 | .tsx(含 JSX),.ts(逻辑) | |
| Props 类型 | 使用 interface 或 type 定义 | |
| 事件类型 | React.ChangeEvent, MouseEvent 等 | |
| 泛型 | 提升复用性和类型安全(如 useFetch<T>) | |
| 工具类型 | Partial, Omit, Pick 提高灵活性 | |
| Context 类型 | 使用 `createContext<Type | undefined>` |
避免 any | 尽量用具体类型替代 |
🎯 下一步预告:
你已经写出类型安全的专业级代码了!接下来我们要进入工程化阶段:
➡️ React 路由:React Router v6
—— 构建多页面 SPA 应用,实现页面跳转、动态路由、权限控制
是否继续?我将带你进入 第八课:React Router v6 实战。