React 开发必备技能:Mock 数据实战指南😋

0 阅读8分钟

作为前端工程师,你是否遇到过这样的场景:产品需求已经确认,UI 设计稿也已交付,但后端接口还在开发中,前端只能对着空页面 “望洋兴叹”?或者测试时因为依赖第三方接口不稳定,导致测试用例频繁失败?这时候,Mock 技术就是解决这些问题的 “神兵利器”。

在 React 开发中,Mock 不仅能打破前后端开发的 “依赖僵局”,还能提升测试效率和代码健壮性。今天这篇文章,我们就从实际开发场景出发,手把手教你在 React 项目中玩转 Mock。

为什么在 React 中使用 Mock

image.png

先给大家算一笔账:如果一个 React 项目需要开发 10 个页面,每个页面依赖 3 个后端接口。假设后端接口平均延迟 2 天交付,按传统开发模式,前端至少要等 60 天(10×3×2)才能完整联调 —— 这显然不符合快速迭代的开发节奏。

而 Mock 的核心价值,就是消除这种 “等待依赖”

  • 前端可以在接口未完成时独立开发,提前完成页面渲染和交互逻辑
  • 能模拟各种边缘场景(如空数据、异常报错、超大列表),提前暴露 UI 和逻辑问题
  • 测试时可精准控制数据返回,让测试用例更稳定

简单来说:Mock 让前端开发从 “被动等待” 变成 “主动推进”

React 中 Mock 的常见应用场景

image.png

开发前期独立开发

这是 Mock 最常用的场景。当后端接口文档刚确定但未开发时,前端可以用 Mock 模拟接口返回,先完成页面渲染和交互。

举个例子:要开发一个用户列表页面,需要展示用户 ID、姓名、头像和角色。此时后端接口还没好,我们可以这样做:

  1. 先定义接口返回格式(和后端约定好):
// 约定的用户列表接口返回格式
{
  "code": 200,
  "message": "success",
  "data": {
    "list": [
      {
        "id": "1",
        "name": "张三",
        "avatar": "https://xxx.com/avatar/1.png",
        "role": "admin"
      }
      // ...更多用户
    ],
    "total": 50,
    "page": 1,
    "pageSize": 10
  }
}
  1. 用 Mock 生成符合格式的模拟数据,在 React 组件中调用 “模拟接口”:
// UserList.jsx
import { useEffect, useState } from 'react';
import axios from 'axios';

const UserList = () => {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    // 调用模拟接口(实际项目中会替换为真实接口地址)
    axios.get('/api/users?page=1&pageSize=10')
      .then(res => {
        setUsers(res.data.data.list);
      });
  }, []);
  
  return (
    <div className="user-list">
      {users.map(user => (
        <div key={user.id} className="user-item">
          <img src={user.avatar} alt={user.name} />
          <div>
            <p>姓名:{user.name}</p>
            <p>角色:{user.role}</p>
          </div>
        </div>
      ))}
    </div>
  );
};

export default UserList;

这样一来,即使后端接口还没开发,前端也能正常开发页面,等接口完成后只需替换请求地址即可。

单元测试与集成测试

React 组件的测试经常需要依赖外部数据(如接口返回、全局状态),而 Mock 能帮我们 “隔离依赖”,让测试更可靠。

比如测试一个登录组件,我们需要验证:

  • 输入账号密码后是否调用登录接口

  • 接口返回成功时是否跳转页面

  • 接口返回失败时是否显示错误提示

此时可以用 Mock 模拟登录接口的返回值,而不用依赖真实后端:

// Login.test.jsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import Login from './Login';
import axios from 'axios';

// Mock axios的post方法
jest.mock('axios');

test('登录成功时跳转到首页', async () => {
  // 模拟登录接口成功返回
  axios.post.mockResolvedValue({
    data: { code: 200, token: 'mock-token' }
  });
  
  // 模拟路由跳转方法
  const mockNavigate = jest.fn();
  jest.mock('react-router-dom', () => ({
    ...jest.requireActual('react-router-dom'),
    useNavigate: () => mockNavigate
  }));
  
  render(<Login />);
  
  // 输入账号密码并点击登录
  fireEvent.change(screen.getByLabelText(/账号/i), { target: { value: 'test' } });
  fireEvent.change(screen.getByLabelText(/密码/i), { target: { value: '123456' } });
  fireEvent.click(screen.getByRole('button', { name: /登录/i }));
  
  // 验证是否调用接口
  expect(axios.post).toHaveBeenCalledWith('/api/login', {
    username: 'test',
    password: '123456'
  });
  
  // 验证是否跳转首页
  await waitFor(() => {
    expect(mockNavigate).toHaveBeenCalledWith('/home');
  });
});

通过 Mock,我们可以精准控制接口返回结果,单独测试 “登录成功”“登录失败” 等场景,避免真实接口波动对测试的影响。

性能与边界测试

在实际场景中,接口可能返回极端数据(如 1000 条列表数据、超长文本),这些情况如果等到联调阶段才发现,可能需要大改组件逻辑。而 Mock 能帮我们提前暴露问题。

比如测试一个商品列表组件的性能,我们可以用 Mock 生成 1000 条商品数据,然后通过 React DevTools 的 Performance 面板分析渲染时间:

// 生成1000条模拟商品数据
const mockProducts = Array.from({ length: 1000 }, (_, i) => ({
  id: i + 1,
  name: `商品${i + 1}`,
  price: Math.floor(Math.random() * 1000),
  // 超长描述,测试文本渲染性能
  description: '这是一段超长的商品描述'.repeat(20),
  image: `https://xxx.com/product/${i + 1}.png`
}));

// 在测试环境中使用该数据渲染组件

通过这种方式,我们能提前发现 “大数据列表渲染卡顿”“超长文本样式错乱” 等问题,避免上线后才修复。

React 中 Mock 的实现方式

image.png

使用 Mock.js

Mock.js 是前端常用的 Mock 库,支持生成随机数据、拦截 Ajax 请求,非常适合开发阶段使用。

基本使用步骤:

  1. 安装依赖:
npm install mockjs --save-dev
  1. 创建 Mock 配置文件(如src/mock/index.js):
import Mock from 'mockjs';

// 模拟用户列表接口
Mock.mock('/api/users', 'get', (options) => {
  // 解析请求参数(如页码、每页条数)
  const { page = 1, pageSize = 10 } = JSON.parse(options.body || '{}');
  
  // 生成随机数据
  return {
    code: 200,
    message: 'success',
    data: {
      total: 100, // 总条数
      list: Mock.mock({
        // 生成pageSize条数据
        [`array|${pageSize}`]: [{
          'id|+1': (page - 1) * pageSize + 1, // 自增ID
          'name': '@cname', // 随机中文姓名
          'age|18-60': 1, // 18-60之间的随机数
          'avatar': '@image(100x100)', // 随机图片
          'role|1': ['admin', 'user', 'guest'] // 随机角色
        }]
      }).array
    }
  };
});

// 模拟登录接口
Mock.mock('/api/login', 'post', (options) => {
  const { username, password } = JSON.parse(options.body);
  
  // 简单验证(实际开发可根据需求调整)
  if (username === 'admin' && password === '123456') {
    return { code: 200, token: 'mock-token' };
  } else {
    return { code: 400, message: '账号或密码错误' };
  }
});
  1. 在项目入口文件中引入(如src/main.js):
// 只在开发环境使用Mock
if (process.env.NODE_ENV === 'development') {
  import('./mock');
}

这样,当项目中发送/api/users的请求时,就会被 Mock.js 拦截并返回模拟数据,无需后端参与。

JSON Server

如果需要更接近真实的 API 服务(如支持 RESTful 风格、数据持久化),可以用JSON Server—— 它能基于 JSON 文件快速搭建一个模拟服务器。

基本使用步骤:

  1. 安装依赖:
npm install json-server --save-dev
  1. 创建数据文件(如db.json):
{
  "users": [
    { "id": 1, "name": "张三", "age": 25 },
    { "id": 2, "name": "李四", "age": 30 }
  ],
  "products": [
    { "id": 1, "name": "手机", "price": 3999 }
  ]
}
  1. package.json中添加脚本:
{
  "scripts": {
    "mock": "json-server --watch db.json --port 3001"
  }
}
  1. 启动服务:
npm run mock

启动后就可以通过以下接口访问数据:

  • GET http://localhost:3001/users 获取所有用户

  • GET http://localhost:3001/users/1 获取 ID 为 1 的用户

  • POST http://localhost:3001/users 添加用户(会自动更新到 db.json)

这种方式的优势是:接口完全符合 RESTful 规范,甚至支持分页、排序、过滤(如?page=1&_limit=10),非常适合需要 “真实接口体验” 的场景。

Jest 测试框架中的 Mock 功能

React 项目常用 Jest 进行测试,而 Jest 内置了强大的 Mock 功能,能模拟函数、模块、定时器等。

常见用法:

  1. 模拟函数返回值:
// 模拟一个计算函数
const mockAdd = jest.fn((a, b) => a + b);

test('模拟函数返回正确结果', () => {
  expect(mockAdd(1, 2)).toBe(3);
  // 验证函数被调用过
  expect(mockAdd).toHaveBeenCalled();
  // 验证调用参数
  expect(mockAdd).toHaveBeenCalledWith(1, 2);
});
  1. 模拟模块导出:
// 模拟工具函数模块
jest.mock('../utils', () => ({
  formatDate: jest.fn((date) => `mock-${date}`)
}));

import { formatDate } from '../utils';

test('模拟工具函数', () => {
  expect(formatDate('2023-01-01')).toBe('mock-2023-01-01');
});
  1. 模拟异步接口:
// 模拟Axios请求
import axios from 'axios';
jest.mock('axios');

test('模拟接口成功返回', async () => {
  axios.get.mockResolvedValue({ data: { code: 200, data: 'success' } });
  const result = await axios.get('/api/test');
  expect(result.data.code).toBe(200);
});

test('模拟接口失败返回', async () => {
  axios.get.mockRejectedValue(new Error('接口错误'));
  await expect(axios.get('/api/test')).rejects.toThrow('接口错误');
});

实战案例:React 电商项目中的 Mock 应用

image.png

以一个简单的电商项目为例,我们来完整实现 Mock 流程。

需求:

开发一个商品列表页面,需要:

  • 展示商品列表(名称、价格、图片)

  • 支持分页(页码切换)

  • 支持搜索(按商品名称筛选)

后端接口还未开发,我们用 Mock 实现整个流程。

实现步骤:

  1. 用 Mock.js 模拟商品接口(src/mock/product.js):
import Mock from 'mockjs';

// 生成模拟商品数据
const generateProducts = (page, pageSize, keyword) => {
  // 总数据量
  const total = 100;
  // 生成基础数据
  const baseList = Mock.mock({
    [`array|${total}`]: [{
      'id|+1': 1,
      'name': '@ctitle(3, 10)', // 随机中文名称
      'price|10-1000': 1, // 随机价格
      'image': '@image(200x200)', // 随机图片
      'sales|0-1000': 1 // 随机销量
    }]
  }).array;
  
  // 搜索筛选
  const filteredList = keyword 
    ? baseList.filter(item => item.name.includes(keyword))
    : baseList;
  
  // 分页处理
  const start = (page - 1) * pageSize;
  const end = start + pageSize;
  const list = filteredList.slice(start, end);
  
  return { list, total: filteredList.length };
};

// 模拟商品列表接口
Mock.mock(//api/products/, 'get', (options) => {
  // 解析URL参数
  const params = new URLSearchParams(options.url.split('?')[1]);
  const page = parseInt(params.get('page') || 1);
  const pageSize = parseInt(params.get('pageSize') || 10);
  const keyword = params.get('keyword') || '';
  
  const { list, total } = generateProducts(page, pageSize, keyword);
  
  return {
    code: 200,
    data: { list, total }
  };
});
  1. 在入口文件引入 Mock(src/main.js):
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// 开发环境引入Mock
if (process.env.NODE_ENV === 'development') {
  import('./mock/product');
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  1. 开发商品列表组件(src/components/ProductList.jsx):
import { useState, useEffect } from 'react';
import axios from 'axios';

const ProductList = () => {
  const [products, setProducts] = useState([]);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(10);
  const [keyword, setKeyword] = useState('');

  // 获取商品数据
  const fetchProducts = async () => {
    try {
      const res = await axios.get('/api/products', {
        params: { page, pageSize, keyword }
      });
      setProducts(res.data.data.list);
      setTotal(res.data.data.total);
    } catch (err) {
      console.error('获取商品失败:', err);
    }
  };

  // 初始加载和参数变化时重新请求
  useEffect(() => {
    fetchProducts();
  }, [page, pageSize, keyword]);

  // 搜索提交
  const handleSearch = (e) => {
    e.preventDefault();
    setPage(1); // 重置到第一页
    fetchProducts();
  };

  return (
    <div className="product-list">
      {/* 搜索框 */}
      <form onSubmit={handleSearch}>
        <input
          type="text"
          value={keyword}
          onChange={(e) => setKeyword(e.target.value)}
          placeholder="请输入商品名称"
        />
        <button type="submit">搜索</button>
      </form>

      {/* 商品列表 */}
      <div className="products-grid">
        {products.map(product => (
          <div key={product.id} className="product-item">
            <img src={product.image} alt={product.name} />
            <h3>{product.name}</h3>
            <p>¥{product.price}</p>
          </div>
        ))}
      </div>

      {/* 分页 */}
      <div className="pagination">
        <button 
          disabled={page === 1}
          onClick={() => setPage(page - 1)}
        >
          上一页
        </button>
        <span>
          第{page}页 / 共{Math.ceil(total / pageSize)}页
        </span>
        <button 
          disabled={page >= Math.ceil(total / pageSize)}
          onClick={() => setPage(page + 1)}
        >
          下一页
        </button>
      </div>
    </div>
  );
};

export default ProductList;
  1. 测试效果:
  • 页面会展示模拟的商品数据

  • 点击分页按钮会切换页码

  • 输入关键词搜索会筛选商品

等后端接口开发完成后,只需删除 Mock 配置,替换请求地址即可,组件逻辑完全不用修改。

总结与展望

image.png

Mock 技术在 React 开发中的价值不言而喻:

  • 开发阶段:打破前后端依赖,让前端独立开发

  • 测试阶段:隔离外部依赖,让测试更稳定可靠

  • 优化阶段:模拟极端场景,提前暴露性能问题

在实际项目中,建议根据场景选择合适的 Mock 方案:

  • 开发阶段用 Mock.js 或 JSON Server

  • 测试阶段用 Jest Mock

  • 需要团队共享接口时,可以考虑 YApi 或 Swagger Mock(接口文档 + Mock 一体工具)

随着前端工程化的发展,Mock 技术也在不断进化 —— 从单纯的 “数据模拟” 向 “接口管理 + 契约测试” 演进。未来,Mock 可能会和 TypeScript 结合更紧密(通过类型定义自动生成 Mock 数据),甚至通过 AI 生成更贴近真实业务的模拟数据。

掌握 Mock,不仅能提升开发效率,更能让你在项目中掌握主动权。希望这篇文章能帮你在 React 开发中用好 Mock 技术,少走弯路!