React生态全景 —— 从项目到产品
写完组件、管好状态、调好性能——到这一步你已经是一个合格的 React 开发者了。但要把一个”能跑”的项目变成一个”能上线并且持续迭代”的产品,还差最后一公里:SEO 怎么办?多语言怎么做?动画怎么加?测试怎么写?部署怎么搞?这些问题的答案不在 React 核心库里,而藏在围绕它生长出的一整片生态森林中。本章将带你鸟瞰这片森林,理清每棵树的位置和价值,让你在真实项目中不再迷路。
📋 开篇自测:你已经知道多少?
- 你能说出 SSR、SSG、ISR 三种渲染策略的区别,以及各自适合什么场景吗?
- 如果要给一个已有项目加上中英文切换,你会选择什么方案?大致步骤是什么?
- React 的 Server Components 和传统 SSR 有什么本质区别?
一、全栈框架 —— 当 React 不再只是前端
1.1 为什么需要全栈框架?
React 本身只是一个 UI 渲染库。你用 create-react-app 或 Vite 搭出来的应用,打包后是一坨 JavaScript 文件,浏览器下载、执行、渲染——这就是典型的 CSR(客户端渲染)。它有两个硬伤:
- SEO 不友好:搜索引擎爬虫看到的是一个空的
<div id="root"></div>,等 JS 执行完才有内容,但很多爬虫等不了那么久。 - 首屏白屏:用户必须等 JavaScript 全部下载并执行完毕,才能看到第一个像素。在弱网环境下,这可能是好几秒的白屏。
全栈框架的核心价值就是把渲染提前——在服务端把组件渲染成 HTML 字符串返回给浏览器,浏览器先展示 HTML,再通过 hydrate(水合) 把交互逻辑”注入”到已有的 DOM 上。
1.2 Next.js:React 官方推荐的全栈方案
Next.js 是 React 生态中最成熟的全栈框架,也是 React 官方文档推荐的首选起步方式。
npx create-next-app@latest my-app --typescript
cd my-app && npm run dev
Next.js 支持三种渲染策略,你可以按页面粒度混合使用:
| 策略 | 全称 | 渲染时机 | 适用场景 |
|---|---|---|---|
| SSR | Server-Side Rendering | 每次请求时渲染 | 用户个性化页面、实时数据 |
| SSG | Static Site Generation | 构建时渲染 | 博客、文档、营销页 |
| ISR | Incremental Static Regeneration | 构建时 + 后台定期刷新 | 商品详情页、新闻列表 |
App Router 下的服务端组件:
Next.js 13+ 引入了 App Router,默认所有组件都是 Server Components——在服务端执行,不会发送 JavaScript 到客户端:
// app/posts/page.tsx —— 默认就是 Server Component
async function PostsPage() {
// 直接在组件里读数据库,不需要 API 中间层
const posts = await db.post.findMany({ orderBy: { createdAt: 'desc' } });
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default PostsPage;
当你需要交互(useState、onClick 等),就在文件顶部加上 'use client' 声明,让这个组件成为客户端组件。
1.3 React Router v7 “Framework Mode”:以 Web 标准为核心的另一条路
Remix 团队已将其核心能力(loader、action、服务端渲染)合并进 React Router v7。现在你不需要单独安装 Remix——React Router v7 的 “framework mode” 就包含了这些功能。它的设计哲学是尽可能贴近浏览器原生的 <form> 提交和 HTTP 语义:
// app/routes/contacts.tsx(React Router v7 framework mode)
import { Form, useLoaderData } from 'react-router';
// loader 在服务端执行,返回的数据自动注入组件
export async function loader() {
const contacts = await getContacts();
return { contacts };
}
// action 处理表单提交
export async function action({ request }) {
const formData = await request.formData();
return createContact(formData);
}
export default function Contacts() {
const { contacts } = useLoaderData();
return (
<>
<Form method="post">
<input name="name" placeholder="姓名" />
<button type="submit">新建联系人</button>
</Form>
<ul>
{contacts.map(c => <li key={c.id}>{c.name}</li>)}
</ul>
</>
);
}
如何选择? 如果你需要极致的 SEO 能力、静态生成、边缘部署,选 Next.js。如果你更看重 Web 标准、渐进增强(即使 JavaScript 加载失败表单也能提交),React Router v7 的 framework mode 是更好的选择。
🤔 想一想 你现在的项目真的需要 SSR 吗?一个后台管理系统(只有登录用户能访问)还需要 SEO 吗?不要为了用技术而用技术——CSR + Vite 对于很多项目来说已经足够好了。
二、UI 组件库 —— 不要重复造轮子
2.1 三大主流方案对比
几乎没有团队会从零写按钮、表格、弹窗。选对组件库能节省几百小时的开发时间。
| 维度 | Ant Design | Material UI | shadcn/ui |
|---|---|---|---|
| 设计语言 | Ant Design(企业级) | Material Design(Google 风格) | Radix + Tailwind(极简) |
| 组件数量 | 60+ | 50+ | 40+(持续增长) |
| 样式方案 | CSS-in-JS / CSS Variables | Emotion / CSS Variables | Tailwind CSS |
| 定制方式 | ConfigProvider + 主题Token | createTheme | 直接修改源码 |
| 打包体积 | 按需引入后较小 | 按需引入后中等 | 零运行时(直接拷贝代码) |
| 适合场景 | 中后台、企业应用 | 面向消费者的产品 | 需要高度定制的项目 |
2.2 shadcn/ui:一种全新的思路
传统组件库是 npm install 一个包,然后 import 组件。shadcn/ui 不是一个 npm 包,而是一套可以直接拷贝到你项目中的组件代码:
# 初始化
npx shadcn@latest init
# 按需添加组件——代码直接生成到你的 src/components/ui 目录
npx shadcn@latest add button
npx shadcn@latest add dialog
npx shadcn@latest add data-table
它的核心理念是:组件代码属于你。你不需要等库作者修 Bug 或者加功能,直接改源码就行。底层基于无样式的 Radix UI 原语,上层用 Tailwind CSS 做样式,兼顾了可访问性和灵活性。
import { Button } from "@/components/ui/button";
import {
Dialog, DialogContent, DialogHeader,
DialogTitle, DialogTrigger
} from "@/components/ui/dialog";
function ConfirmDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">打开确认框</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>确定要删除吗?</DialogTitle>
</DialogHeader>
<div className="flex justify-end gap-2 mt-4">
<Button variant="ghost">取消</Button>
<Button variant="destructive">确认删除</Button>
</div>
</DialogContent>
</Dialog>
);
}
三、动画方案 —— 让界面动起来
CSS 的 transition 和 animation 可以覆盖简单场景,但当你需要编排多个元素的连续动画、弹簧物理效果、手势驱动的交互动画时,就需要专业的动画库了。
3.1 Framer Motion(motion):声明式动画的标杆
Framer Motion 的 API 设计极其直觉——你只需要声明”从哪来”和”到哪去”,中间的过渡它自动处理。注意:Framer Motion 现已更名为 motion,新项目推荐使用 motion/react 导入路径(旧的 framer-motion 包名仍可使用但不再更新)。
import { motion, AnimatePresence } from 'motion/react'; // 新包名(原 framer-motion)
function FadeInCard({ isVisible }) {
return (
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
className="card"
>
我会淡入淡出
</motion.div>
)}
</AnimatePresence>
);
}
AnimatePresence 是 Framer Motion 的杀手级功能——它能在组件卸载前播放退出动画,这是纯 CSS 做不到的事情。
布局动画同样强大——只需加一个 layout 属性,元素位置和尺寸变化时会自动产生平滑过渡:
function ExpandableCard({ isExpanded }) {
return (
<motion.div
layout
style={{
width: isExpanded ? 400 : 200,
height: isExpanded ? 300 : 100
}}
className="rounded-lg bg-blue-500"
/>
);
}
3.2 react-spring:基于物理的弹簧动画
react-spring 不使用 duration(持续时间),而是模拟真实世界的弹簧物理——通过质量(mass)、张力(tension)、摩擦力(friction)来控制动画:
import { useSpring, animated } from '@react-spring/web';
function SpringBox() {
const [flipped, setFlipped] = useState(false);
const styles = useSpring({
transform: flipped ? 'rotateX(180deg)' : 'rotateX(0deg)',
config: { mass: 1, tension: 200, friction: 20 }
});
return (
<animated.div
style={styles}
onClick={() => setFlipped(f => !f)}
className="w-40 h-40 bg-gradient-to-r from-purple-500 to-pink-500 rounded-lg cursor-pointer"
/>
);
}
| 维度 | Framer Motion | react-spring |
|---|---|---|
| API 风格 | 声明式 props | Hook 驱动 |
| 动画模型 | 时间/弹簧均支持 | 纯弹簧物理 |
| 布局动画 | 内置 layout | 需手动计算 |
| 退出动画 | AnimatePresence | useTransition |
| 包大小 | ~34KB | ~25KB |
| 推荐场景 | 页面过渡、复杂交互 | 数据驱动的弹性动效 |
🤔 想一想 你做过的项目中,有没有一个地方如果加上动画,用户体验会明显提升?比如列表项的增删、页面的切换、加载状态的过渡?试着用 Framer Motion 的三行代码(initial/animate/exit)去改造它。
四、拖拽交互 —— 用手指重新排列世界
拖拽排序、看板布局、文件上传预览——这些功能在现代 Web 应用中越来越常见。
4.1 react-dnd:经典的拖拽方案
react-dnd 基于 HTML5 Drag and Drop API,通过 useDrag 和 useDrop 两个 Hook 来实现拖拽:
import { useDrag, useDrop, DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
function DraggableCard({ id, text, moveCard }) {
const [{ isDragging }, drag] = useDrag({
type: 'CARD',
item: { id },
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});
const [, drop] = useDrop({
accept: 'CARD',
hover: (draggedItem) => {
if (draggedItem.id !== id) {
moveCard(draggedItem.id, id);
}
}
});
return (
<div
ref={(node) => drag(drop(node))}
style={{ opacity: isDragging ? 0.4 : 1 }}
className="p-4 mb-2 bg-white rounded shadow cursor-move"
>
{text}
</div>
);
}
// 使用时需要 DndProvider 包裹
function App() {
return (
<DndProvider backend={HTML5Backend}>
<KanbanBoard />
</DndProvider>
);
}
4.2 dnd-kit:更现代的替代方案
dnd-kit 是更新一代的拖拽库,API 更简洁,性能更好,默认支持键盘拖拽(可访问性),不依赖 HTML5 Drag and Drop API:
import {
DndContext, closestCenter,
KeyboardSensor, PointerSensor, useSensor, useSensors
} from '@dnd-kit/core';
import {
SortableContext, verticalListSortingStrategy,
useSortable
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
function SortableItem({ id, text }) {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition
};
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}
className="p-4 mb-2 bg-white rounded shadow cursor-move">
{text}
</div>
);
}
function SortableList({ items }) {
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor)
);
return (
<DndContext sensors={sensors} collisionDetection={closestCenter}>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map(item => (
<SortableItem key={item.id} id={item.id} text={item.text} />
))}
</SortableContext>
</DndContext>
);
}
选型建议: 新项目建议直接用 dnd-kit——API 更符合 Hooks 时代的心智模型,可访问性开箱即用,且在触控设备上表现更好。
五、国际化 —— 让应用说全世界的语言
如果你的应用需要支持中英文切换(甚至更多语言),就需要一套国际化(i18n)方案。核心思路很简单:把所有用户可见的文本抽离到语言包中,运行时根据当前语言加载对应的包。
5.1 react-i18next:社区最流行的方案
react-i18next 基于 i18next 核心库,提供了 React 专用的 Hook 和组件:
npm install react-i18next i18next
第一步:定义语言包
// src/i18n/locales/zh.json
{
"welcome": "欢迎回来,{{name}}",
"nav": {
"home": "首页",
"about": "关于我们",
"contact": "联系我们"
},
"items_count": "共 {{count}} 项"
}
// src/i18n/locales/en.json
{
"welcome": "Welcome back, {{name}}",
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"items_count": "{{count}} items in total"
}
第二步:初始化
// src/i18n/index.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import zh from './locales/zh.json';
import en from './locales/en.json';
i18n.use(initReactI18next).init({
resources: {
zh: { translation: zh },
en: { translation: en }
},
lng: 'zh', // 默认语言
fallbackLng: 'en', // 找不到翻译时的回退语言
interpolation: { escapeValue: false }
});
export default i18n;
第三步:在组件中使用
import { useTranslation } from 'react-i18next';
function NavBar() {
const { t, i18n } = useTranslation();
const toggleLang = () => {
i18n.changeLanguage(i18n.language === 'zh' ? 'en' : 'zh');
};
return (
<nav>
<a href="/">{t('nav.home')}</a>
<a href="/about">{t('nav.about')}</a>
<a href="/contact">{t('nav.contact')}</a>
<button onClick={toggleLang}>
{i18n.language === 'zh' ? 'English' : '中文'}
</button>
</nav>
);
}
function Dashboard({ userName, itemCount }) {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome', { name: userName })}</h1>
<p>{t('items_count', { count: itemCount })}</p>
</div>
);
}
5.2 react-intl:FormatJS 系的老牌方案
react-intl 是 FormatJS 生态的一部分,在日期、数字、货币的本地化格式化上更强大:
import { IntlProvider, FormattedMessage, FormattedNumber } from 'react-intl';
function App() {
const [locale, setLocale] = useState('zh');
const messages = locale === 'zh' ? zhMessages : enMessages;
return (
<IntlProvider locale={locale} messages={messages}>
<p>
<FormattedMessage id="welcome" values={{ name: '小明' }} />
</p>
<p>
价格:<FormattedNumber value={1234.5} style="currency" currency="CNY" />
{/* 输出:¥1,234.50 */}
</p>
</IntlProvider>
);
}
选型建议: 如果你的应用以文本翻译为主,react-i18next 更轻量、API 更简单。如果你需要大量的日期/数字/货币格式化(比如金融应用),react-intl 的 FormattedNumber、FormattedDate 更称手。
六、测试体系 —— 写完代码只是成功了一半
代码写完了能跑,不代表它是对的。测试是把”我觉得没问题”变成”我能证明没问题”的唯一方式。
6.1 三层测试金字塔
/ E2E 测试 \ ← 少量:关键用户流程
/ 集成测试 \ ← 适量:组件交互、数据流
/ 单元测试 \ ← 大量:纯函数、工具方法
- 单元测试:验证单个函数或 Hook 的行为是否正确。
- 集成测试:验证多个组件组合在一起是否正常工作。
- E2E 测试(端到端):模拟真实用户操作浏览器,验证整个流程。
6.2 Vitest + React Testing Library:单元/集成测试
Vitest 是 Vite 生态的测试框架,速度极快,API 兼容 Jest:
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
// vite.config.ts 中添加测试配置
/// <reference types="vitest" />
import { defineConfig } from 'vite';
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test/setup.ts'
}
});
// src/test/setup.ts
import '@testing-library/jest-dom';
测试一个计数器组件:
// Counter.tsx
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<span data-testid="count">{count}</span>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<button onClick={() => setCount(0)}>重置</button>
</div>
);
}
// Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Counter } from './Counter';
describe('Counter', () => {
it('初始值为 0', () => {
render(<Counter />);
expect(screen.getByTestId('count')).toHaveTextContent('0');
});
it('点击 +1 按钮后计数增加', () => {
render(<Counter />);
fireEvent.click(screen.getByText('+1'));
fireEvent.click(screen.getByText('+1'));
expect(screen.getByTestId('count')).toHaveTextContent('2');
});
it('点击重置按钮后回到 0', () => {
render(<Counter />);
fireEvent.click(screen.getByText('+1'));
fireEvent.click(screen.getByText('重置'));
expect(screen.getByTestId('count')).toHaveTextContent('0');
});
});
npx vitest run # 运行一次
npx vitest # watch 模式
6.3 Playwright:E2E 测试
Playwright 可以驱动真实浏览器(Chromium、Firefox、WebKit),模拟用户的完整操作流程:
npm install -D @playwright/test
npx playwright install
// e2e/login.spec.ts
import { test, expect } from '@playwright/test';
test('用户登录流程', async ({ page }) => {
await page.goto('http://localhost:3000/login');
// 填写表单
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
// 断言跳转到首页
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('欢迎回来');
});
实用建议: 不要追求 100% 测试覆盖率。把精力花在最重要的地方——支付流程、登录注册、核心业务逻辑。一个好的经验法则是:如果这段代码出了 Bug 会让你半夜被叫起来,那就给它写测试。
🤔 想一想 很多团队说”没时间写测试”。但你有没有算过,一个线上 Bug 从发现、定位、修复、上线到安抚用户,花费的时间是多少?对比之下,提前写好测试的投入产出比如何?
七、部署与发布 —— 让代码跑在用户的浏览器里
开发完成后,最后一步是把应用部署到服务器上,让全世界都能访问。
7.1 Vercel:React 项目的最佳部署平台
Vercel 是 Next.js 的官方部署平台,但它支持所有主流前端框架。对于个人项目和小团队来说,它可能是最省心的选择:
# 安装 Vercel CLI
npm install -g vercel
# 在项目根目录执行
vercel
# 就这样,你的应用上线了
# 它会自动检测框架、构建、部署、分配域名
Vercel 的核心优势:
- 零配置:自动检测 Next.js / Vite / CRA 等框架,自动配置构建命令。
- 预览部署:每个 Git 分支 / PR 自动生成独立的预览链接。
- 边缘网络:静态资源自动分发到全球 CDN。
- Serverless Functions:API 路由自动部署为无服务器函数。
7.2 Docker:当你需要更多控制权
在企业环境中,你通常需要把应用部署到自己的服务器或 Kubernetes 集群。这时候 Docker 是标准方案:
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# 构建镜像
docker build -t my-react-app .
# 运行容器
docker run -d -p 80:80 my-react-app
7.3 CI/CD 集成:让发布变成一键操作
用 GitHub Actions 实现推送到 main 分支后自动部署:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test -- --run
- run: npm run build
# 部署到你选择的平台
- name: Deploy to Server
run: |
# rsync、scp、Docker push 等
echo "部署完成"
关键原则:任何发布流程都应该包含三道门——lint 检查、自动化测试、构建验证。任何一道门没过,都不应该部署到生产环境。
八、React 的未来 —— 正在发生的变革
React 的发展从未停止。以下三个方向正在深刻改变 React 的使用方式。
8.1 Server Components:前后端的边界正在模糊
Server Components 不是 SSR 的升级版——它们是一个全新的心智模型。
传统 SSR:组件在服务端渲染成 HTML → 发送到客户端 → 客户端重新执行所有组件代码进行 hydrate。组件的 JavaScript 代码仍然需要发送到客户端。
Server Components:组件在服务端执行 → 渲染结果(不是 HTML,而是一种特殊的序列化格式)流式发送到客户端 → 客户端不需要这些组件的 JavaScript 代码。
// 这个组件的代码永远不会出现在客户端的 JS bundle 中
// 你可以直接 import 任何 Node.js 模块
import { readFile } from 'fs/promises';
import { marked } from 'marked'; // 这个库也不会发送到客户端
async function BlogPost({ slug }) {
const markdown = await readFile(`./posts/${slug}.md`, 'utf-8');
const html = marked(markdown);
return <article dangerouslySetInnerHTML={{ __html: html }} />;
}
直观收益:如果你用了一个 200KB 的 Markdown 解析库,在 Server Components 中使用它,客户端的打包体积完全不受影响。
8.2 React Compiler:告别手动 memo
React Compiler(原名 React Forget)已于 2025 年 10 月稳定发布。它是一个独立的编译工具(需要单独安装配置,不是 React 19 包的一部分),能在编译阶段自动插入 memoization 代码,让你不再需要手写 useMemo、useCallback、React.memo:
// 你只管写最自然的代码
function ProductList({ products, category }) {
const filtered = products.filter(p => p.category === category);
return (
<ul>
{filtered.map(p => (
<ProductCard key={p.id} product={p} />
))}
</ul>
);
}
// React Compiler 会在编译时自动分析依赖关系
// 自动为 filtered 加上 memoization
// 自动为 ProductCard 的 props 加上浅比较优化
// 你什么都不用做
这意味着”性能优化”将从一项需要专业判断的手动工作,变成编译器自动完成的事情。你写代码的方式不用变,但运行性能会自动提升。
8.3 新的 API 方向
React 19 还带来了一系列简化开发体验的新 API:
// useActionState —— 简化表单提交状态管理
function LoginForm() {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const result = await login(formData);
if (result.error) return { error: result.error };
redirect('/dashboard');
},
{ error: null }
);
return (
<form action={formAction}>
<input name="email" type="email" />
<input name="password" type="password" />
{state.error && <p className="text-red-500">{state.error}</p>}
<button disabled={isPending}>
{isPending ? '登录中...' : '登录'}
</button>
</form>
);
}
// use() —— 在组件中直接读取 Promise 和 Context
function UserProfile({ userPromise }) {
const user = use(userPromise); // 等同于 await,但在客户端组件中可用
return <h1>{user.name}</h1>;
}
九、生态选型速查表
最后,给你一份实际项目中的技术选型速查表,方便你在启动新项目时快速决策:
| 领域 | 推荐方案 | 备选方案 | 选型要点 |
|---|---|---|---|
| 全栈框架 | Next.js | Remix | SEO 需求 → Next.js;表单密集 → Remix |
| 组件库 | shadcn/ui | Ant Design | 高度定制 → shadcn;快速交付 → Antd |
| 样式 | Tailwind CSS | CSS Modules | 原子化效率 → Tailwind;传统习惯 → Modules |
| 状态管理 | Zustand | Jotai | 全局 store → Zustand;原子化状态 → Jotai |
| 动画 | Framer Motion | react-spring | 通用动画 → FM;弹簧物理 → spring |
| 拖拽 | dnd-kit | react-dnd | 新项目 → dnd-kit;已有项目 → react-dnd |
| 国际化 | react-i18next | react-intl | 文本翻译 → i18next;日期货币 → intl |
| 单元测试 | Vitest + RTL | Jest + RTL | Vite 项目 → Vitest;Webpack → Jest |
| E2E 测试 | Playwright | Cypress | 多浏览器 → Playwright;开发体验 → Cypress |
| 部署 | Vercel | Docker + Nginx | 快速上线 → Vercel;企业私有化 → Docker |
记住一条选型的核心原则:不要选最强的,选最匹配你当前需求的。 一个两人团队的内部工具和一个面向百万用户的消费产品,技术选型应该截然不同。
📝 掌握度自测
学完本章后,试着回答以下问题来检验自己的掌握程度:
- Next.js 的 App Router 中,Server Components 和 Client Components 的区别是什么?什么时候需要加
'use client'? - shadcn/ui 和传统的 npm 组件库(如 Ant Design)在使用方式上有什么根本区别?各自的优缺点是什么?
- Framer Motion 的
AnimatePresence解决了什么问题?为什么纯 CSS 动画做不到这件事? - 请描述 Vitest + React Testing Library 测试一个带有异步数据加载的组件的完整步骤。
- React Compiler 的目标是什么?它如何改变我们编写 React 代码的方式?
💡 自我评估
- 能答出 3 题以上:你已经对 React 生态有了整体认知,可以在实际项目中做出合理的技术选型。
- 能答出 1-2 题:建议挑选你最感兴趣的 1-2 个方向,用一个小项目实际练手。
- 一题都答不上来:不要焦虑——这一章是全景概览,每个方向都值得花一周去深入。建议从你当前项目最迫切需要的一个工具开始,边学边用。
购买课程解锁全部内容
从组件到架构:12 章系统掌握现代 React
¥29.90