使用 Mock.js 在 Vite + React 项目中模拟后端 API 接口

6 阅读7分钟

在现代前端开发中,前后端分离已成为主流模式。这种模式下,前端工程师不再依赖后端接口的实时可用性,而是可以通过工具模拟数据接口,从而加速开发流程。前端开发者会经常遇到项目启动阶段后端接口尚未就绪的情况。这时,使用 Mock.js 来伪造 API 响应就显得尤为实用。它不仅能生成随机数据,还支持分页、查询参数解析等功能,让前端代码在开发环境中运行顺畅,上线时只需切换 baseURL 即可无缝过渡。

本文将基于一个简单的帖子列表(Post List)场景,详细讲解如何在 Vite + React 项目中集成 Mock.js。我们会从接口文档阅读开始,逐步实现数据模拟、axios 配置、API 调用、Zustand 状态管理,以及 React 组件渲染。

前后端分离下的数据挑战

首先,我们来谈谈数据来源。在一个典型的帖子列表功能中,真实数据存储在后端数据库,前端通过 HTTP 请求(如 axios)调用 API 获取。例如,接口文档定义了一个 GET 请求:/api/posts?page=1&limit=10,返回格式为:

JSON

{
  "status": 200,
  "list": [ /* Post 数组 */ ]
}

这里的 Post 对象可能包含标题、简介、发布时间、用户信息、标签、缩略图等字段。如果后端接口还未开发完成,前端是否需要等待?答案是否定的。在前后端分离的基础上,前端可以自行 mock 数据。通过伪造请求响应,前端开发者可以独立推进 UI 开发和逻辑测试,一旦后端接口上线,只需修改 axios 的 baseURL,就能实现无缝对接。

这种方法的核心优势在于:

  • 并行开发:前后端团队同时开工,提高效率。
  • 测试友好:模拟各种场景,如分页、空数据、错误响应。
  • 减少依赖:避免因后端延误导致前端停滞。

为了实现 mock,我们选择 Mock.js 这个轻量级库。它支持随机数据生成、模板语法,并能拦截 axios 请求。在 Vite 项目中,Mock.js 的集成非常简单,通常结合 vite-plugin-mock 等插件启动 mock 服务。

Mock.js 的基础使用

Mock.js 的语法简洁强大。我们先来看如何在项目中设置 mock 文件。以帖子列表为例,创建一个 mock/posts.js 文件,用于定义 API 路由和响应逻辑。

Mock.js 提供了 @ 占位符来生成随机数据,例如:

  • @ctitle(8, 20):生成 8-20 个中文字符的标题。
  • @integer(1, 30):生成 1-30 的整数。
  • @datetime("yyyy-MM-dd HH:mm"):生成指定格式的日期时间。
  • @image(300x200):生成 300x200 的随机图片 URL。
  • @cname(2, 4):生成 2-4 个中文字符的姓名。

此外,Mock.js 支持数组生成,如 'list|45' 表示生成 45 条数据的数组。我们定义一个 tags 数组(如 ["前端", "后端", "职场", "AI", "副业", "面经", "算法"]),然后使用 Mock.Random.pick(tags, 2) 随机挑选 2 个标签。

以下是完整的 mock/posts.js 示例代码:

JavaScript

import Mock from 'mockjs'
const tags = ["前端", "后端", "职场", "AI", "副业", "面经", "算法"]
const posts = Mock.mock({
  'list|45': [  // list 45 条
    {
      title: '@ctitle(8, 20)',
      brief: '@ctitle(20, 100)',
      totalComments: '@integer(1, 30)',
      totalLinks: '@integer(0, 500)',
      publishedAt: '@datetime("yyyy-MM-dd HH:mm")',
      user: {
        id: '@integer(1, 100)',
        name: '@cname(2, 4)',
        avatar: '@image(300x200)',
      },
      tags: () => Mock.Random.pick(tags, 2),
      thumbnail: '@image(300x200)',
      pics: [
        '@image(300x200)',
        '@image(300x200)',
        '@image(300x200)',
      ],
      id: '@increment(1)'
    }
  ]
}).list

export default [
  {
    url: '/api/posts',
    methods: 'GET',
    response:({ query }, res) => {
      // console.log(query, ' ????');
      const { page = '1', limit = '10' } = query;
      const currentPage = parseInt(page, 10);
      const size = parseInt(limit, 10);

      if (isNaN(currentPage) || isNaN(size) || currentPage < 1 || size < 1) {
        return {
          code: 400,
          msg: 'Invalid page or pageSize',
          data: null,
        }
      }
      
      const total = posts.length;  // count
      const start = (currentPage - 1) * size;
      const end = start + size;
      const pageinatedData = posts.slice(start, end);

      return {
        code: 200,
        msg: 'success',
        items: pageinatedData,
        pagination: {
          currentPage,
          limit: size,
          total,
          totalPage: Math.ceil(total/size),
        }
      }
    }
  }
]

这个代码首先生成 45 条假帖子数据,然后在响应函数中解析查询参数(page 和 limit),实现分页逻辑:

  • 使用 parseInt 转换参数,确保数值有效。
  • 计算起始索引 start 和结束索引 end。
  • 通过 slice 方法截取当前页数据。
  • 返回包含 items(当前页数据)和 pagination(分页信息)的对象。

注意,返回格式调整为 { code: 200, msg: 'success', items: ..., pagination: ... },这比接口文档中的简单 { status: 200, list: ... } 更丰富,便于前端处理分页 UI。如果实际接口不同,可以轻松修改。

在 Vite 项目中,需要配置 vite-plugin-mock 来加载这个文件。通常在 vite.config.js 中添加插件:

JavaScript

import { defineConfig } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'

export default defineConfig({
  plugins: [
    viteMockServe({
      mockPath: 'mock', // mock 文件目录
      localEnabled: true, // 开发环境启用
      prodEnabled: false, // 生产环境禁用
    }),
  ],
})

这样,在开发模式下,axios 请求 /api/posts 会被 Mock.js 拦截,返回模拟数据。

Axios 配置与 API 封装

接下来,我们配置 axios 以支持 mock。在 src/api/config.ts 中设置 baseURL 为本地地址:

TypeScript

import axios from 'axios';
// 接口地址都以/api开始
axios.defaults.baseURL = 'http://localhost:5173/api'
// axios.defaults.baseURL = 'http://douyin:5173/api'

export default axios;

这里,开发环境使用 http://localhost:5173/api ,生产环境可以切换到真实后端(如注释中的地址)。这种方式确保 mock 无缝过渡。

然后,在 src/api/posts.ts 中封装 fetchPosts 函数:

TypeScript

import axios from './config';

import type { Post } from '@/types';

export const fetchPosts = async (page: number = 1, limit: number = 10) => {
  try {
    const response = await axios.get('/posts', {
      params: {
        page, 
        limit
      }
    });
    console.log(response);
    return response.data;
  } catch(err) {
    
  }
}

这个函数使用 axios 发送 GET 请求,传递 page 和 limit 参数。注意,catch 块目前为空,在实际开发中应添加错误处理,如返回默认数据或抛出错误。同时,types/Post 来自 types/index.ts,定义了 Post 接口:

TypeScript

export interface User {
  id: number;
  name: string;
  avatar?: string;
}

export interface Post {
  id: number;
  title: string;
  brief: string;  // 简介
  publishedAt: string;  // 发布时间
  totalLinkes?: number;
  totalComments?: number;
  user: User;
  tags: string[];
  thumbnail: string;  // 缩略图
  pics?: string[];
}

接口中 totalLinkes 可能是拼写错误,应为 totalLikes,但为了保持一致,我保留原样。如果是笔误,可以在文章中指正:在类型定义中,totalLinkes 可能意为 likes,但建议改为 totalLikes 以提高可读性。

Zustand 状态管理与数据加载

在 React 项目中,使用 Zustand 管理全局状态非常高效。我们创建一个 store/home.ts 来存储 banners 和 posts:

TypeScript

import { create } from 'zustand';
import type { SlideData } from '@/components/SlideShow'
import type { Post } from '@/types';
import { fetchPosts } from '@/api/posts';

interface HomeState {
  banners: SlideData[];
  posts: Post[];
  loadMore: () => Promise<void>
}

const useHomeStore = create<HomeState>((set) => ({
  banners: [{
      id: 1,
      title: "React 生态系统",
      image: "https://images.unsplash.com/photo-1633356122544-f134324a6cee?q=80&w=2070&auto=format&fit=crop",
    },
    {
      id: 2,
      title: "移动端开发最佳实践",
      image: "https://img.36krcdn.com/hsossms/20260114/v2_1ddcc36679304d3390dd9b8545eaa57f@5091053@ai_oswg1012730oswg1053oswg495_img_png~tplv-1marlgjv7f-ai-v3:600:400:600:400:q70.jpg?x-oss-process=image/format,webp",
    },
    {
      id: 3,
      title: "百度上线七猫漫剧,打的什么主意?",
      image: "https://img.36krcdn.com/hsossms/20260114/v2_8dc528b02ded4f73b29b7c1019f8963a@5091053@ai_oswg1137571oswg1053oswg495_img_png~tplv-1marlgjv7f-ai-v3:600:400:600:400:q70.jpg?x-oss-process=image/format,webp",
  }],
  posts: [],
  loadMore: async () => {
    const { items } = await fetchPosts();
  }
}))

export default useHomeStore;

这里,banners 是静态轮播数据,posts 初始化为空数组。loadMore 函数调用 fetchPosts 获取数据,但存在一个问题:它没有使用 set 更新 posts 状态!这会导致数据加载后无法反映到组件中。优化建议:在 loadMore 中添加 set:

TypeScript

loadMore: async () => {
  const data = await fetchPosts();
  set({ posts: data.items });
}

此外,如果需要支持加载更多(load more),可以添加 page 变量,并实现无限滚动或按钮加载。

React 组件集成

最后,在 src/pages/Home.tsx 中使用 store 渲染页面:

typescriptreact

import Header from '@/components/Header'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import SlideShow, {type SlideData} from '@/components/SlideShow';
import useHomeStore from '@/store/home'
import { useEffect } from 'react';

const Home = () => {
  const { banners, posts, loadMore } = useHomeStore();

  useEffect(() => {
    loadMore();
  }, []);
  
  return (
    <>
      <Header title="首页" showBackBtn={true}></Header>
      <div className="p-4 space-y-4">
        <SlideShow slides={banners}/>
        <Card>
          <CardHeader>
            <CardTitle>
              欢迎来到React Mobile
            </CardTitle>
          </CardHeader> 
          <CardContent>
            <p className="text-muted-foreground">
              这是内容区域
            </p>
          </CardContent>
        </Card>
        <div className="grid grid-cols-2 gap-4">
          {
            [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,25, 26].map((i, index) => (
              <div key={index} className="h-32 bg-white rouded-lg
                shadow-sm flex items-center justify-center border
              ">
                Item {i}
              </div>
            ))
          }
        </div>
      </div>
    </>
  )
}

export default Home;

优化点:

  • 错误处理:在 mock 中添加更多错误场景模拟,如 404 或 500。
  • 无限加载:在 store 中添加 page 状态,实现 loadMore 时递增 page。
  • 类型安全:使用 TypeScript,确保所有接口一致。
  • 性能:如果数据量大,考虑虚拟列表渲染 posts。

总结

通过 Mock.js,我们在 Vite + React 项目中轻松模拟了后端 API,实现了帖子列表的分页功能。这种方法适用于任何前后端分离项目,帮助开发者高效协作。实际应用中,根据接口文档调整 mock 逻辑,就能应对复杂场景。