跳到主要内容
预计阅读 50 分钟

React生态全景 —— 从项目到产品

写完组件、管好状态、调好性能——到这一步你已经是一个合格的 React 开发者了。但要把一个”能跑”的项目变成一个”能上线并且持续迭代”的产品,还差最后一公里:SEO 怎么办?多语言怎么做?动画怎么加?测试怎么写?部署怎么搞?这些问题的答案不在 React 核心库里,而藏在围绕它生长出的一整片生态森林中。本章将带你鸟瞰这片森林,理清每棵树的位置和价值,让你在真实项目中不再迷路。

📋 开篇自测:你已经知道多少?

  1. 你能说出 SSR、SSG、ISR 三种渲染策略的区别,以及各自适合什么场景吗?
  2. 如果要给一个已有项目加上中英文切换,你会选择什么方案?大致步骤是什么?
  3. 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 支持三种渲染策略,你可以按页面粒度混合使用:

策略全称渲染时机适用场景
SSRServer-Side Rendering每次请求时渲染用户个性化页面、实时数据
SSGStatic Site Generation构建时渲染博客、文档、营销页
ISRIncremental 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;

当你需要交互(useStateonClick 等),就在文件顶部加上 '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 DesignMaterial UIshadcn/ui
设计语言Ant Design(企业级)Material Design(Google 风格)Radix + Tailwind(极简)
组件数量60+50+40+(持续增长)
样式方案CSS-in-JS / CSS VariablesEmotion / CSS VariablesTailwind CSS
定制方式ConfigProvider + 主题TokencreateTheme直接修改源码
打包体积按需引入后较小按需引入后中等零运行时(直接拷贝代码)
适合场景中后台、企业应用面向消费者的产品需要高度定制的项目

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 的 transitionanimation 可以覆盖简单场景,但当你需要编排多个元素的连续动画、弹簧物理效果、手势驱动的交互动画时,就需要专业的动画库了。

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 Motionreact-spring
API 风格声明式 propsHook 驱动
动画模型时间/弹簧均支持纯弹簧物理
布局动画内置 layout需手动计算
退出动画AnimatePresenceuseTransition
包大小~34KB~25KB
推荐场景页面过渡、复杂交互数据驱动的弹性动效

🤔 想一想 你做过的项目中,有没有一个地方如果加上动画,用户体验会明显提升?比如列表项的增删、页面的切换、加载状态的过渡?试着用 Framer Motion 的三行代码(initial/animate/exit)去改造它。


四、拖拽交互 —— 用手指重新排列世界

拖拽排序、看板布局、文件上传预览——这些功能在现代 Web 应用中越来越常见。

4.1 react-dnd:经典的拖拽方案

react-dnd 基于 HTML5 Drag and Drop API,通过 useDraguseDrop 两个 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 的 FormattedNumberFormattedDate 更称手。


六、测试体系 —— 写完代码只是成功了一半

代码写完了能跑,不代表它是对的。测试是把”我觉得没问题”变成”我能证明没问题”的唯一方式。

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 代码,让你不再需要手写 useMemouseCallbackReact.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.jsRemixSEO 需求 → Next.js;表单密集 → Remix
组件库shadcn/uiAnt Design高度定制 → shadcn;快速交付 → Antd
样式Tailwind CSSCSS Modules原子化效率 → Tailwind;传统习惯 → Modules
状态管理ZustandJotai全局 store → Zustand;原子化状态 → Jotai
动画Framer Motionreact-spring通用动画 → FM;弹簧物理 → spring
拖拽dnd-kitreact-dnd新项目 → dnd-kit;已有项目 → react-dnd
国际化react-i18nextreact-intl文本翻译 → i18next;日期货币 → intl
单元测试Vitest + RTLJest + RTLVite 项目 → Vitest;Webpack → Jest
E2E 测试PlaywrightCypress多浏览器 → Playwright;开发体验 → Cypress
部署VercelDocker + Nginx快速上线 → Vercel;企业私有化 → Docker

记住一条选型的核心原则:不要选最强的,选最匹配你当前需求的。 一个两人团队的内部工具和一个面向百万用户的消费产品,技术选型应该截然不同。


📝 掌握度自测

学完本章后,试着回答以下问题来检验自己的掌握程度:

  1. Next.js 的 App Router 中,Server Components 和 Client Components 的区别是什么?什么时候需要加 'use client'
  2. shadcn/ui 和传统的 npm 组件库(如 Ant Design)在使用方式上有什么根本区别?各自的优缺点是什么?
  3. Framer Motion 的 AnimatePresence 解决了什么问题?为什么纯 CSS 动画做不到这件事?
  4. 请描述 Vitest + React Testing Library 测试一个带有异步数据加载的组件的完整步骤。
  5. React Compiler 的目标是什么?它如何改变我们编写 React 代码的方式?

💡 自我评估

  • 能答出 3 题以上:你已经对 React 生态有了整体认知,可以在实际项目中做出合理的技术选型。
  • 能答出 1-2 题:建议挑选你最感兴趣的 1-2 个方向,用一个小项目实际练手。
  • 一题都答不上来:不要焦虑——这一章是全景概览,每个方向都值得花一周去深入。建议从你当前项目最迫切需要的一个工具开始,边学边用。

购买课程解锁全部内容

从组件到架构:12 章系统掌握现代 React

¥29.90