React:Umi + React + Ant Design Pro的基础上接入Mock数据

为什么需要Mock数据

前端开发依赖后端接口时的阻塞问题

独立开发和测试的需求

快速迭代和原型验证的重要性

当前版本及框架

React18

Umi 4.0

Ant Design + Ant Design Pro

其实这些都不重要,主要是有Umijs,因为Umijs具有开箱即用Mock功能的能力,可参考官网文档:UmiJS-Mock

实践

前提

如果你想使用Umi里面的mock,那么必须安装Umi框架,用npm或者pnpm就正常安装,此步骤不在赘述,以下直接实践

第一步 创建一个mock文件

不用纠结这个 文件是放在src下面,还是谁的下面,直接就这个项目里面,与src平级关系

第二步 在mock创建对应的ts文件,放置模拟数据和接口

mock/ip.ts

typescript 复制代码
const MOCK_DATA = [
  {
    id: '1',
    used: 10,
    total: 14,
    region: '洛杉矶',
    coreRoute: 'US',
    exitNode: 'Uplink',
    remark: 'TikTok直播专用',
  },
  {
    id: '2',
    used: 13,
    total: 14,
    region: '洛杉矶',
    coreRoute: 'US',
    exitNode: 'Uplink',
    remark: 'TikTok直播专用',
  },
  {
    id: '3',
    used: 0,
    total: 14,
    region: '洛杉矶',
    coreRoute: 'US',
    exitNode: 'Uplink',
    remark: 'Amazon电商专用',
  }
];

// 抽屉列表的专用数据源(按段 id 区分)
const IP_PERMISSION_DATA: Record<string, any[]> = {
......// 数组包裹数组
};

export default {
  // 列表(保留)
  'POST /api/ip/list': (req: any, res: any) => {
    setTimeout(() => {
      res.send({ success: true, data: MOCK_DATA, total: MOCK_DATA.length });
    }, 120);
  },

  // 获取单条详情:根据 body.id 返回对应 record
  'POST /api/ip/detail': (req: any, res: any) => {
    const { id } = req.body || {};
    const item = MOCK_DATA.find((it) => String(it.id) === String(id));
    setTimeout(() => {
      res.send({ success: true, data: item || null });
    }, 80);
  },

  // 更新(简单模拟,body 包含 id 与其他字段)
  'POST /api/ip/update': (req: any, res: any) => {
    const payload = req.body || {};
    const idx = MOCK_DATA.findIndex((it) => String(it.id) === String(payload.id));
    if (idx >= 0) {
      MOCK_DATA[idx] = { ...MOCK_DATA[idx], ...payload };
    }
    setTimeout(() => {
      res.send({ success: true, message: '更新成功(mock)', data: MOCK_DATA[idx] || null });
    }, 120);
  },

  // 权限管理抽屉列表
  'POST /api/ip/children': (req: any, res: any) => {
    const { id, cidr } = req.body || {};
    let key = id ? String(id) : undefined;
    if (!key && cidr) {
      const found = MOCK_DATA.find((it) => String(it.cidr) === String(cidr));
      key = found?.id;
    }
    const data = (key && IP_PERMISSION_DATA[key]) || [];
    setTimeout(() => res.send({ success: true, data, total: data.length }), 100);
  },
};

第三步 调用mock数据和接口

放在某个调用接口的.tsx文件里面

typescript 复制代码
  // 列表数据(来自 /mock/ip.ts)
  const [ipList, setIpList] = useState<any[]>([]);

  // 加载列表(调用 mock 的 GET /api/ip/list,后端到位时直接替换)
  const loadIpList = useCallback(
    async (opts?: { page?: number; pageSize?: number }) => {
      const page = opts?.page || currentPage;
      const size = opts?.pageSize || pageSize;

      const payload = {
        page,
        pageSize: size,
        keyword: keyWord || '',
        source: filterSource || '',
        coreRoute: filterCoreRoute || '',
      };

      try {
        const res = await fetch('/api/ip/list', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload),
        });

        // 期望后端/Mock 返回 JSON 格式 { success: true, data: [...], total: n }
        const ct = (res.headers.get('content-type') || '').toLowerCase();
        if (!ct.includes('application/json')) {
          console.warn('loadIpList non-json response');
          setIpList([]);
          setTotal(0);
          return;
        }

        const json = await res.json();
        if (json && json.success) {
          setIpList(json.data || []);
          setTotal(json.total || 0);
        } else if (json && Array.isArray(json.data)) {
          // 兜底兼容
          setIpList(json.data || []);
          setTotal(json.total || json.data.length || 0);
        } else {
          setIpList([]);
          setTotal(0);
        }
      } catch (err) {
        console.error('loadIpList error', err);
        setIpList([]);
        setTotal(0);
      }
    },
    [currentPage, pageSize, keyWord, filterSource, filterCoreRoute],
  );

  // 获取单条详情(调用 mock 的 POST /api/ip/detail)
  const getIpDetail = useCallback(async (id: string | number) => {
    try {
      const res = await fetch('/api/ip/detail', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ id }),
      });
      const json = await res.json();
      if (json && json.success) return json.data;
      return null;
    } catch (err) {
      console.error('getIpDetail error', err);
      return null;
    }
  }, []);

  // 更新 IP 段(调用 mock 的 POST /api/ip/update),成功后刷新列表
  const updateIp = useCallback(
    async (payload: any) => {
      try {
        const res = await fetch('/api/ip/update', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload),
        });
        const json = await res.json();
        if (json && json.success) {
          // 刷新当前页
          loadIpList({ page: currentPage, pageSize });
          return { ok: true, data: json.data };
        }
        return { ok: false, error: json };
      } catch (err) {
        console.error('updateIp error', err);
        return { ok: false, error: err };
      }
    },
    [loadIpList, currentPage, pageSize],
  );
typescript 复制代码
// 比如这个列表接口调用
useEffect(() => {
  loadIpList();
}, [currentPage, pageSize, filterSource, filterRegion, keyWord]);

第四步 注意是否开启mock

以我此刻的项目为例,

npm run start