学习目标
- 1理解React组件的类型和使用场景
- 2学会用TypeScript定义Props类型
- 3掌握常用Hooks的TypeScript用法
- 4学会组件组合和泛型组件
回顾与问题引入
上一章我们学会了用API路由处理数据。现在博客有了"厨房"——能处理订单(请求)、做菜(业务逻辑)、送餐(响应)。
但是,我们的"餐厅"(页面)还很粗糙——所有代码都堆在一个文件里,没有"标准化的餐具"(可复用组件)。
问题场景:
你的博客有10个页面,每个页面都有一个"文章卡片"。如果每个页面都写一遍卡片的代码:
- 代码重复,维护困难
- 改一处要改10处
- 样式不一致
解决方案:React组件
类比理解:
React组件就像乐高积木:
- 每个积木有特定的功能(按钮、卡片、表单)
- 积木可以组合成更大的结构(页面、布局)
- 积木可以重复使用(复用性)
- 积木有标准化的接口(Props)
本章目标: 学会用TypeScript构建可复用的React组件,让你的博客代码更整洁、更专业。
什么是React组件?
React组件是构建UI的基本单元。它是一个函数,接收一些输入(Props),返回一段UI(JSX)。
类比理解:
组件就像自动贩卖机:
- Props = 你投入的钱和选择的按钮(输入)
- 组件内部 = 机器的处理逻辑
- 返回的JSX = 掉出来的商品(输出)
TypeScript的优势:
| 方面 | 没有TypeScript | 有TypeScript | |------|---------------|-------------| | Props检查 | 运行时才发现传错 | 编译时就报错 | | 自动补全 | 要看文档才知道传什么 | IDE自动提示 | | 重构 | 容易漏改 | 类型系统帮你找全 | | 团队协作 | 要看代码才知道用法 | 类型就是文档
函数组件基础
children属性:组件的插槽
很多组件需要"包裹"其他内容。比如卡片组件需要包含任意内容,对话框需要包含表单或文字。
类比理解:
children就像快递箱里的东西:
- 快递箱(组件)是固定的
- 里面装什么(children)是灵活的
- 可以装书、装衣服、装任何东西
类型选择:
| 类型 | 适用场景 |
|------|---------|
| React.ReactNode | 最灵活,接受任何可渲染内容(推荐) |
| string | 只接受纯文本 |
| React.ReactElement | 只接受JSX元素 |
| React.ReactNode[] | 接受多个子元素 |
带Children的组件
常用Hooks的TypeScript用法
Hooks是React的"魔法工具",让你在函数组件中使用状态和其他React特性。
类比理解:
| Hook | 类比 | 作用 | |------|------|------| | useState | 记事本 | 记住组件的状态 | | useEffect | 定时器 | 处理副作用(数据获取、订阅) | | useRef | 便利贴 | 引用DOM元素或保存值 | | useContext | 公告栏 | 跨组件共享数据 |
关键点:
- Hooks只能在函数组件的顶层使用
- 不能在循环、条件或嵌套函数中使用
- TypeScript会自动推断Hook的类型
Hooks的TypeScript用法
事件处理的类型
泛型组件:一物多用
有时候你需要一个组件,能处理不同类型的数据。比如一个列表组件,既能显示文章列表,也能显示用户列表。
类比理解:
泛型组件就像快递箱的尺寸规格:
- 箱子的结构是固定的(列表的渲染逻辑)
- 里面装的东西是灵活的(不同类型的数据)
- 你只需要告诉它"装什么"(泛型参数)
泛型组件的语法:
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map(renderItem)}</ul>;
}
这里的T是类型参数,使用时TypeScript会自动推断。
泛型组件实战
常见错误
常见错误
错误1:Props类型定义不完整
// 错误!没有定义Props类型
export function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
// 正确
interface ButtonProps {
label: string;
onClick: () => void;
}
export function Button({ label, onClick }: ButtonProps) {
return <button onClick={onClick}>{label}</button>;
}
错误2:忘记可选属性的?标记
// 错误!variant是必需的
interface ButtonProps {
label: string;
variant: string; // 使用时必须传
}
// 正确
interface ButtonProps {
label: string;
variant?: string; // 可选,有默认值
}
错误3:children类型错误
// 错误!string太严格了
interface CardProps {
children: string; // 只能传文字
}
// 正确
interface CardProps {
children: React.ReactNode; // 可以传任何内容
}
错误4:useState类型推断错误
// 错误!初始值是null,但后续会设置User对象
const [user, setUser] = useState(null); // user: null
setUser({ name: "Alice" }); // 类型错误!
// 正确
const [user, setUser] = useState<User | null>(null);
错误5:事件处理类型错误
// 错误!使用了原生DOM事件类型
const handleClick = (e: MouseEvent) => {};
// 正确
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {};
动手实践
现在轮到你了!让我们动手练习:
练习1:创建文章卡片组件 创建一个PostCard组件,接收文章数据并展示:
- Props: title (string), content (string), author (string), date (Date)
- 显示文章标题、内容摘要、作者和日期
- 支持children(用于添加"阅读更多"链接)
练习2:创建搜索组件 创建一个SearchBar组件:
- Props: onSearch (回调函数), placeholder (可选)
- 包含输入框和搜索按钮
- 支持按回车键搜索
练习3:创建泛型表格组件 创建一个Table组件:
- 泛型:Table<T>
- Props: columns (列定义), data (数据数组)
- 能够显示任意类型的数据
提示:
- 使用interface定义Props类型
- 可选属性用?标记
- 事件处理使用React的事件类型
本章总结
恭喜你完成了React组件的学习!让我们回顾一下:
本章学到的:
- 组件是构建UI的基本单元,就像乐高积木
- 用interface定义Props类型,让组件有"说明书"
- children属性让组件更灵活,就像快递箱
- Hooks让函数组件有"记忆"和"生命周期"
- 泛型组件让一个组件处理多种数据类型
前后对比:
| 方面 | 之前(无组件化) | 现在(组件化) | |------|---------------|-------------| | 代码复用 | 复制粘贴 | 组件复用 | | 维护成本 | 改一处要改多处 | 改组件定义即可 | | 类型安全 | 手动检查 | TypeScript自动检查 | | 开发效率 | 慢 | 快(积木式开发) |
下一章预告:
现在我们有了页面、API、组件——是时候把它们组合成一个完整的博客项目了!下一章是实战章,我们将从零搭建一个完整的博客系统,综合运用前面学到的所有知识。
交互式练习
创建用户卡片组件
创建一个UserCard组件,展示用户信息
章节测验
如何定义React组件的Props类型?