第 18 章:组件库
学习目标
- 了解 React 主流组件库及其特点
- 掌握 shadcn/ui 的安装与使用
- 用 Tailwind 快速构建自定义组件
- 避开组件库的 4 个常见坑
一、主流组件库对比
| 组件库 | 风格 | 大小 | 特点 |
|---|---|---|---|
| Material UI (MUI) | Material Design | 大 | 功能最全 |
| Ant Design | 企业后台 | 大 | 中后台首选 |
| Chakra UI | 简洁现代 | 中 | 易用 |
| shadcn/ui | 复制粘贴 | 小 | 可定制 |
| Radix UI | 无样式 | 小 | 行为/可访问性 |
| Heroicons | 图标 | - | 配合任意库 |
二、shadcn/ui(2025 推荐)
不是传统组件库,而是复制源码到项目里,完全可控:
bash
npx shadcn-ui@latest init
npx shadcn-ui@latest add button card dialog生成的组件在 src/components/ui/:
tsx
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
function Demo() {
return (
<Card>
<CardHeader>
<CardTitle>标题</CardTitle>
</CardHeader>
<CardContent>
<Button>点击</Button>
</CardContent>
</Card>
);
}优势:
- 样式基于 Tailwind + CSS 变量
- 完全可改源码
- 暗色模式开箱即用
- 基于 Radix UI,可访问性优秀
三、MUI(企业级)
bash
npm install @mui/material @emotion/react @emotion/styledtsx
import { Button, TextField, Stack } from '@mui/material';
function Demo() {
return (
<Stack spacing={2}>
<TextField label="用户名" variant="outlined" />
<Button variant="contained">提交</Button>
</Stack>
);
}四、Ant Design(中后台)
bash
npm install antdtsx
import { Button, Form, Input, Table } from 'antd';
function Demo() {
return (
<Form>
<Form.Item name="username">
<Input placeholder="用户名" />
</Form.Item>
<Button type="primary" htmlType="submit">提交</Button>
</Form>
);
}五、Headless UI(无样式逻辑)
适合自己设计样式时使用,提供行为但不带样式:
bash
npm install @headlessui/reacttsx
import { Dialog } from '@headlessui/react';
function Demo() {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onClose={() => setOpen(false)}>
<Dialog.Panel>
<Dialog.Title>标题</Dialog.Title>
<p>内容</p>
<button onClick={() => setOpen(false)}>关闭</button>
</Dialog.Panel>
</Dialog>
);
}六、图标库
bash
npm install lucide-react # shadcn 默认
# 或
npm install @heroicons/react
# 或
npm install react-iconstsx
import { Search, Plus, Trash2 } from 'lucide-react';
<button>
<Plus className="w-4 h-4" /> 新建
</button>⚠️ 坑 1:按需 import,不要
import * as Icons from 'lucide-react'然后<Icons.Search />,打包体积爆炸。
七、表单组件
react-hook-form + zod(组合推荐)
tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email('邮箱格式错误'),
password: z.string().min(6, '至少 6 位'),
});
type FormData = z.infer<typeof schema>;
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input type="password" {...register('password')} />
{errors.password && <span>{errors.password.message}</span>}
<button>提交</button>
</form>
);
}八、数据表格
tsx
import { DataGrid } from '@mui/x-data-grid';
const columns = [
{ field: 'id', headerName: 'ID', width: 90 },
{ field: 'name', headerName: '姓名', width: 150 },
];
< DataGrid rows = { users } columns = { columns } />或自建(更灵活):
tsx
function DataTable({ rows, columns }: Props) {
return (
<table>
<thead>
<tr>{columns.map(c => <th key={c.key}>{c.title}</th>)}</tr>
</thead>
<tbody>
{rows.map(row => (
<tr key={row.id}>
{columns.map(c => <td key={c.key}>{row[c.key]}</td>)}
</tr>
))}
</tbody>
</table>
);
}⚠️ 坑 2:大数据表格(>1000 行)用虚拟滚动(
@tanstack/react-virtual),否则卡顿。
九、暗色模式
tsx
// next-themes
import { ThemeProvider } from 'next-themes';
<ThemeProvider attribute="class">
<App />
</ThemeProvider>
// 使用
import { useTheme } from 'next-themes';
const { theme, setTheme } = useTheme();
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>切换</button>⚠️ 坑 3:暗色模式一定要防闪烁(
FOUC),用class属性 + SSR 注入 class。
十、本章小结
| 要点 | 关键 |
|---|---|
| 选型 | 中后台用 Antd/MUI,定制用 shadcn |
| shadcn | 复制源码,可控,Tailwind 友好 |
| 按需引入 | 不要全量 import 图标/组件 |
| 表单 | react-hook-form + zod 组合 |
| 大表格 | 用虚拟滚动 |
| 暗色模式 | next-themes 防闪烁 |
动手练习
- shadcn 初始化:在 Next.js 项目里
initshadcn,加 Button/Card 组件 - 登录页:用 shadcn 的 Input + Button + Card 拼一个登录页
- 图标使用:在按钮里加 lucide-react 图标,演示 hover 效果
- 找坑:以下代码打包后会怎样?tsx
import * as Icons from 'lucide-react'; <Icons.Search />