React 从入门到生产(八):测试与部署

创作者: Yardon | GitHub: github.com/YardonYan | 版本: v1.0


为什么要写测试

测试是一种投资------短期成本是写代码的时间,长期回报是减少线上事故和重构恐惧。

测试金字塔:

复制代码
        /\
       /E2E\        少(关键路径)
      /------\
     / 集成测试 \     中(组件交互)
    /----------\
   /  单元测试    \   多(纯函数、组件、Hook)
  /--------------\

Vitest + Testing Library 入门

bash 复制代码
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
javascript 复制代码
// vitest.config.js
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,  // 无需 import describe/it/expect
    setupFiles: './test-setup.js',
  },
});

组件测试实战

jsx 复制代码
// Counter.jsx
export function Counter({ initial = 0 }) {
  const [count, setCount] = useState(initial);
  return (
    <div>
      <span data-testid="count">{count}</span>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
      <button onClick={() => setCount(c => c - 1)}>-1</button>
    </div>
  );
}
jsx 复制代码
// Counter.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';

describe('Counter', () => {
  it('初始值正确', () => {
    render(<Counter initial={5} />);
    expect(screen.getByTestId('count')).toHaveTextContent('5');
  });

  it('点击 +1 增加', () => {
    render(<Counter />);
    fireEvent.click(screen.getByText('+1'));
    expect(screen.getByTestId('count')).toHaveTextContent('1');
  });

  it('点击 -1 减少', () => {
    render(<Counter initial={10} />);
    fireEvent.click(screen.getByText('-1'));
    expect(screen.getByTestId('count')).toHaveTextContent('9');
  });
});

测试原则

  1. 测试行为,不测试实现------如果你改了组件内部代码但行为不变,测试应该仍然通过
  2. 不测试第三方库------React 是 Facebook 的责任,不是你的
  3. 每个测试只验证一件事

Hook 测试

jsx 复制代码
import { renderHook, act } from '@testing-library/react';

it('useCounter: increment 增加计数', () => {
  const { result } = renderHook(() => useCounter(0));

  act(() => result.current.increment());
  expect(result.current.count).toBe(1);

  act(() => result.current.increment());
  act(() => result.current.increment());
  expect(result.current.count).toBe(3);
});

E2E 测试:Playwright

bash 复制代码
npm install -D @playwright/test
npx playwright install
javascript 复制代码
// tests/smoke.spec.js
import { test, expect } from '@playwright/test';

test('首页加载正常', async ({ page }) => {
  await page.goto('http://localhost:4173');
  await expect(page.locator('h1')).toHaveText('欢迎');
});

test('导航到博客页', async ({ page }) => {
  await page.goto('http://localhost:4173');
  await page.click('text=博客');
  await expect(page).toHaveURL(/\/blog/);
});

构建与部署:Vite + Nginx

bash 复制代码
vite build           # 产出 dist/ 目录
nginx 复制代码
server {
    listen 80;
    server_name example.com;
    root /var/www/react-app/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;  # SPA 路由回退
    }

    location /assets/ {
        expires 1y;          # 静态资源缓存一年
        add_header Cache-Control "public, immutable";
    }
}

CI/CD 流水线

yaml 复制代码
# .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 }
      - run: npm ci
      - run: npm run lint
      - run: npm run test
      - run: npm run build
      - name: Deploy
        run: rsync -avz dist/ user@server:/var/www/app/

系列总结

欢迎来到「React 从入门到生产」系列的终点。

回顾我们这八章走过的路:

主题 核心收获
1 JSX 与组件思维 React 的声明式范式、组件化思想
2 状态与事件处理 useState、受控组件、不可变更新
3 副作用与数据获取 useEffect、竞态处理、清理函数
4 自定义 Hook 封装可复用逻辑、Hook 组合模式
5 状态管理选型 Context vs Zustand vs Redux 的适用场景
6 路由与导航 React Router v6、嵌套路由、懒加载
7 性能优化 React.memo、虚拟滚动、代码分割
8 测试与部署 Vitest、Playwright、Nginx 部署

React 不是一门需要"学完"的技术------它是一个持续进化的生态系统。真正重要的是理解它的核心范式(声明式 UI、单向数据流、组件思维),然后不断在实践中打磨。

📌 创作者: Yardon | 🏠 个人网站: GlimmerAI.top

📖 本章是「React 从入门到生产」系列的终章。完整 8 章已完结!

🎉 恭喜你完成了 React 学习之旅!下一路线:「FastAPI 全栈后端」------从路由设计到生产部署。欢迎大家来观看!

相关推荐
DreamLife☼10 分钟前
OpenBCI-脑机接口在康复医疗中的应用
深度学习·cnn·脑电·康复·fes·openbci·外骨骼
kyriewen31 分钟前
Git Commit 前自动修复代码风格?配置 Husky + lint-staged,从此 CR 只聊逻辑
前端·git·面试
小和尚同志40 分钟前
AI 自动化测试探索(一):Playwright MCP
前端·人工智能·aigc
硅谷秋水1 小时前
面向长上下文自动驾驶的规划对齐Token压缩
人工智能·深度学习·机器学习·计算机视觉·自动驾驶
郭泽斌之心1 小时前
MQL5 EA 怎么和外部程序通信?文件三件套协议:参数热更新不重启、状态心跳、远程触发
人工智能·经验分享·深度学习·ea·fay数字人·easydeal
老马识途2.01 小时前
在AI的帮助下理解spring的启动过程
java·前端·spring
AI人工智能+1 小时前
智能文档抽取系统以专业的文档解析底座和大模型智能语义理解能力为核心,洞察文档的语义内涵与逻辑结构
深度学习·自然语言处理·ocr·文档抽取
nap-joker2 小时前
用于转录组信息精确肿瘤学和药物机制分析的多模态可解释深度学习
人工智能·深度学习·药物敏感性·多层级生物网络·细胞异质性·可解释性多模态
徐小夕2 小时前
Loop Engineering 深度解析与实战指南(全网最全)
前端·算法·github
运筹vivo@2 小时前
Python ContextVar 底层机制与内存模型拆解
前端·数据库·python