前端資料模擬方案

發表於2023-09-19
我與我周旋久,寧作我

在前端開發中,使用 Mock 方案可以幫助開發人員在不依賴於後端介面的情況下進行開發和除錯。Mock 資料可以模擬後端介面的返回結果,使得前端開發可以獨立進行,不受後端介面的限制。以下是幾種常見的前端Mock 方案:

手動編寫Mock資料

手動編寫 Mock 資料是一種簡單而直接的前端Mock 方案。你可以手動建立 JSONJavaScript 物件來模擬後端介面的返回資料。下面是一個示例,展示如何手動編寫 Mock 資料:

// 模擬使用者列表介面的Mock資料
const mockUsers = [
  { id: 1, name: "John Doe", email: "john@example.com" },
  { id: 2, name: "Jane Smith", email: "jane@example.com" },
  { id: 3, name: "Bob Johnson", email: "bob@example.com" }
];

const sleep = (wait) => new Promise((resolve) => setTimeout(resolve, wait));

// 模擬獲取使用者列表的介面
axios.get("/api/users").then(() => sleep(500)).then(() => mockUsers);

這種方式適用於簡單的場景,但對於複雜的介面或大量資料的情況下,手動編寫 Mock 資料可能會變得繁瑣。

Mock.js

Mock.js 是一個前端模擬資料生成器,可以幫助開發人員快速生成隨機資料,並攔截 Ajax 請求。它提供了豐富的資料模板語法和模擬請求攔截功能,可以模擬後端介面的返回結果。Mock.js 可以輕鬆整合到前端專案中,並與其他前端框架(如VueReact等)配合使用。

在引入 Mock.js 庫時,會執行初始化操作。這包括建立全域性的 Mock 物件和生成 MockXMLHttpRequest 建構函式來完整地模擬原生 XHR(XMLHttpRequest)的行為。然後,當呼叫 Mock.mock() 方法時,Mock.js 開始解析資料模板,並生成模板資料,儲存到快取變數中。

為了能夠攔截和處理前端傳送的 AJAX 請求,Mock.js 將全域性的 window.XMLHttpRequest 物件替換為 MockXMLHttpRequest。由於許多前端框架和庫(如 axios)底層也使用了 XMLHttpRequest 傳送網路請求,現在 XMLHttpRequest 被替換成了 MockXMLHttpRequest,從而使得 Mock.js 能夠攔截這些請求。

當使用 axios 或其他基於 XMLHttpRequest 的網路請求庫傳送請求時,如果有匹配的資料模板,MockXMLHttpRequest 將會返回模擬資料,並將請求和響應狀態同步給 axios(比如 axios 中監聽了 readystatechange 事件,MockXMLHttpRequest 模擬響應後需要觸發 readystatechange)。這樣,前端開發人員可以在開發和除錯過程中使用模擬資料,而無需實際訪問後端介面。

如果沒有匹配的資料模板,MockXMLHttpRequest 內部會呼叫原生 XMLHttpRequest 傳送請求,並將請求和響應狀態同步給 axios,以便實際的網路請求能夠正常進行。

透過以上流程,Mock.js 可以方便地攔截和模擬前端的網路請求,提供模擬資料,以支援前端開發和除錯的需求。

當在 React 中使用 Mock.js,你可以透過以下示例來模擬網路請求和使用模擬資料:

import React from 'react';
import Mock from 'mockjs';
import axios from 'axios';

class MyComponent extends React.Component {
  componentDidMount() {
    // 定義資料模板和攔截規則
    Mock.mock('/api/users', 'get', {
      'list|5': [{
        'id|+1': 1,
        'name': '@cname',
        'age|18-60': 1
      }]
    });

    // 攔截請求並延遲響應
    Mock.setup({
        timeout: '1000-3000'
    });

    // 使用模擬資料
    axios.get('/api/users')
      .then(response => {
        console.log(response.data); // 模擬資料
        // 處理模擬資料邏輯
      })
      .catch(error => {
        // 處理錯誤邏輯
      });
  }

  render() {
    // 元件渲染邏輯
    return (
      // JSX 介面程式碼
    );
  }
}

在上面的示例中,我們在 componentDidMount() 生命週期方法中定義了一個 GET 請求的資料模板和攔截規則,並使用模擬資料來處理網路請求。當元件掛載後,Mock.js 會攔截 /api/usersGET 請求並返回模擬資料。在使用 axios 發起網路請求後,可以透過 then 回撥處理返回的模擬資料。

雖然 Mock.js 是一個強大的工具,但也存在一些侷限性,包括:

  • 無法模擬複雜的後端邏輯:Mock.js 主要用於模擬介面返回的資料,但無法完全模擬後端的複雜邏輯和業務流程(增刪改)。對於需要與後端進行深度互動的場景,Mock.js 可能無法提供準確的模擬資料和行為。
  • 需要手動編寫資料模板和攔截規則:使用 Mock.js 需要手動編寫資料模板和攔截規則,這對於複雜的資料結構和介面規範可能需要花費較多的時間和精力。對於大型專案或頻繁變動的介面,維護這些規則可能會帶來一定的工作量。
  • 對於使用非 XMLHttpRequest 的網路請求庫支援有限:Mock.js 主要攔截和模擬 XMLHttpRequest 的請求,對於使用其他網路請求庫(如 fetch)的專案,Mock.js 的支援可能相對有限。需要額外的配置和適配才能與這些庫進行整合。

axios-mock-adapter

Axios 例項的 defaults.adapter 屬性用於配置介面卡,預設情況下,Axios 使用 xhrAdapter 作為介面卡,基於瀏覽器的 XMLHttpRequest 物件傳送請求。而在 Node.js 環境中,Axios 使用 httpAdapter 作為介面卡,利用 Node.jshttp 模組傳送請求。

image.png

axios-mock-adapter 是一個與 Axios 配合使用的外掛。它透過例項化一個 mockAdapter 物件,並將其指定給 Axios 例項的 defaults.adapter 屬性,從而將其作為介面卡。

當使用 Axios 傳送請求時,Axios 例項會根據 defaults.adapter 配置的介面卡來處理請求。在正常情況下,請求會被髮送到伺服器,並返回伺服器的響應。然而,當 defaults.adapter 被設定為 mockAdapter 時,axios-mock-adapter 將攔截匹配的請求,並返回預先定義的模擬資料,而不會實際傳送請求到伺服器。

function adapter() {
  return function (config) {
    var mockAdapter = this;
    return new Promise(function (resolve, reject) {
      handleRequest(mockAdapter, resolve, reject, config);
    });
  }.bind(this);
}

function MockAdapter(axiosInstance, options) {
  reset.call(this);

  if (axiosInstance) {
    this.axiosInstance = axiosInstance;
    // Clone the axios instance to remove interceptors
    // this is used for the passThrough mode with axios > 1.2
    this.axiosInstanceWithoutInterceptors = axiosInstance.create
      ? axiosInstance.create()
      : undefined;

    this.originalAdapter = axiosInstance.defaults.adapter;
    this.delayResponse =
      options && options.delayResponse > 0 ? options.delayResponse : null;
    this.onNoMatch = (options && options.onNoMatch) || null;
    // 指定介面卡
    axiosInstance.defaults.adapter = this.adapter.call(this);
  } else {
    throw new Error("Please provide an instance of axios to mock");
  }
}

function handleRequest(mockAdapter, resolve, reject, config) {
  // code...

  var handler = utils.findHandler(
    mockAdapter.handlers,
    config.method,
    url,
    config.data,
    config.params,
    (config.headers && config.headers.constructor.name === 'AxiosHeaders')
      ? Object.assign({}, config.headers)
      : config.headers,
    config.baseURL
  );

  if (handler) {
    // 有匹配路由,返回模擬資料
  } else {
    // 沒有匹配路由,走實際請求
  }
}

以下是在 React 中使用 axios-mock-adapter 示例:

import React, { useEffect } from 'react';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

const YourComponent = () => {
  useEffect(() => {
    const mock = new MockAdapter(axios);
    // 在這裡設定模擬請求的規則和響應
    mock.onGet('/api/endpoint').reply(200, { data: 'mocked data' });
    mock.onPost('/api/endpoint').reply(200, { data: 'mocked data' });

    // 使用 Axios 傳送請求
    axios.get('/api/endpoint').then((response) => {
      console.log(response.data);
    });
  }, []);

  return <div>...</div>;
};

axios-mock-adapter 是一個用於模擬和測試 Axios 請求的強大工具,然而,使用 axios-mock-adapter 也有一些缺點,主要缺點就是 axios-mock-adapter 只能與 axios 搭配使用,不能支援複雜的業務場景(增刪改)。

webpack-dev-mock

mock-dev-server 是一個基於 Express 的中介軟體,它允許你自定義中介軟體來攔截請求,並根據路由匹配返回模擬資料,否則將繼續傳送實際請求。

以下是 mock-dev-server 關鍵核心程式碼:

app.use(function (req, res, next) {
    // 請求攔截,匹配路由
    var match = mockConfig.length && (0, matchPath_1.default)(req, mockConfig);
    if (match) {
        debug("mock matched: [" + match.method + "] " + match.path);
        // 如果有匹配路由,則執行自定義中介軟體
        return match.handler(req, res, next);
    }
    else {
        // 如果沒有匹配路由,則執行後續中介軟體行為,傳送實際請求
        return next();
    }
});

在使用 React 進行開發時,你可以透過 webpack-dev-server 搭建本地開發伺服器,並結合 mock-dev-server 中介軟體來實現請求攔截和模擬資料返回的功能。webpack-dev-server 提供了一個名為 before 的回撥函式,會自動查詢專案中是否存在 setupProxy.js 檔案,並將其作為配置檔案來新增中介軟體。

before(app, server) {
    // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware`
    // middlewares before `redirectServedPath` otherwise will not have any effect
    // This lets us fetch source contents from webpack for the error overlay
    app.use(evalSourceMapMiddleware(server));
    // This lets us open files from the runtime error overlay.
    app.use(errorOverlayMiddleware());
    // 自動查詢專案中是否存在 setupProxy.js 檔案
    if (fs.existsSync(paths.proxySetup)) {
        // This registers user provided middleware for proxy reasons
        require(paths.proxySetup)(app);
    }
},

以下是 React 專案使用 mock-dev-server 模擬資料的示例:

(1)配置模擬資料

// mock/index.js
module.exports = {
    // 支援引數
    'POST /api/users/:id': (req, res) => {
        const { id } = req.params;
        res.send({ id: id });
    },
}

(2)在 setupProxy.js 新增自定義中介軟體

const proxy = require('http-proxy-middleware');
const mockDevServer = require('mock-dev-server');
module.exports = function (app) {
    mockDevServer(app); // 預設讀取專案目錄下的 mock/index.js 檔案生成,會配置對應的介面。

    // 代理到開發環境
    app.use(proxy([
        "/api/xxx"
    ], {
      target: 'https://example.com/',
      changeOrigin: true,
    }));
};

mock-dev-server 是一個在前端開發中常用的工具,用於實現請求攔截和模擬資料返回的功能。它具有以下優點和缺點:

優點:

  • 靈活性:mock-dev-server 允許開發人員自定義中介軟體來攔截請求並返回模擬資料,因此具有很高的靈活性。你可以根據需要定義路由和對應的模擬資料,滿足專案的特定需求。
  • 易於整合:mock-dev-server 基於 Express,與常見的前端開發工具和框架整合較為容易。你可以將其作為 webpack-dev-server 的中介軟體來使用,或者與其他構建工具或框架搭配使用,如 create-react-appVue CLI 等。

缺點:

  • 無法模擬複雜的業務場景:儘管 mock-dev-server 可以模擬後端介面的響應,但它無法模擬後端的業務邏輯。對於一些複雜的業務場景或後端計算,可能無法完全模擬真實的後端行為。

json-server

json-server 是一個基於 Node.js 的工具,可以根據提供的 JSON 檔案快速搭建一個 RESTful API 的臨時伺服器。開發人員可以將需要模擬的資料儲存在 JSON 檔案中,然後使用 json-server 啟動一個本地伺服器,前端透過傳送 HTTP 請求來獲取資料。這個方案簡單易用,適用於快速搭建臨時的 Mock 伺服器。

json-server 使用 Express 來搭建服務和管理路由。當你提供了路由配置檔案時,json-server 將按照該檔案中定義的規則來生成路由。這樣你可以更靈活地定義資源的路由和操作方式。如果沒有提供路由配置檔案,json-server 會根據資料檔案的結構自動生成預設的路由,包括對資料的增刪改查操作。

此外,json-server 還支援自定義中介軟體。你可以新增自己的中介軟體函式來實現額外的功能,例如身份驗證、日誌記錄等。這樣你可以在 API 請求的不同階段進行自定義處理。

另一個重要的特性是,當資料檔案發生變更時,json-server 會自動重新啟動服務。這意味著你可以輕鬆地修改和更新資料檔案,而無需手動停止和重新啟動伺服器,從而提高開發效率。

以下是一個使用 json-server 的簡單示例:

首先,安裝所需的依賴:

$ yarn add json-server global

接下來,建立一個名為 db.json 的資料檔案,作為 json-server 的資料來源。在該檔案中,可以定義一些示例資料,例如:

{
  "posts": [
    { "id": 1, "title": "Post 1", "content": "This is the content of Post 1" },
    { "id": 2, "title": "Post 2", "content": "This is the content of Post 2" }
  ]
}

然後,建立一個名為 api.js 的檔案,以使用 axios 庫來傳送 HTTP 請求。示例程式碼如下:

import axios from 'axios';

const api = axios.create({
  baseURL: '/',
});

export const getPosts = () => api.get('/posts');
export const getPost = (id) => api.get(`/posts/${id}`);
export const createPost = (data) => api.post('/posts', data);
export const updatePost = (id, data) => api.put(`/posts/${id}`, data);
export const deletePost = (id) => api.delete(`/posts/${id}`);

const apis = {
  getPosts,
  getPost,
  createPost,
  updatePost,
  deletePost,
};

export default apis;

接下來,建立一個名為 Posts.js 的元件,用於展示帖子列表。該元件將使用 api.js 中定義的函式來獲取帖子資料。示例程式碼如下:

import React, { useEffect, useState } from 'react';
import api from './api';

const Posts = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const response = await api.getPosts();
      setPosts(response.data);
    };

    fetchData();
  }, []);

  return (
    <div>
      <h2>Posts</h2>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Posts;

現在,在終端中執行以下命令啟動 json-server

$ json-server --watch db.json --port 3001

配置反向代理:

// setupProxy.js
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
  app.use(proxy([
    "/posts",
  ], {
    target: 'http://localhost:3001',
    changeOrigin: true,
  }));
};

然後,在另一個終端視窗中執行以下命令以啟動 React 應用程式:

$ yarn start

Chrome Local Overrides

Chrome 117 版本開始,引入了一項新功能,它允許開發者覆蓋XHR(XMLHttpRequest)fetch請求的內容。您可以在不依賴真實後端資料的情況下進行開發和除錯。您可以自定義響應,模擬各種不同的情況,如成功響應、錯誤響應等,以確保您的網頁在各種場景下都能正常執行。

Chrome 瀏覽器的 Network 皮膚中,您可以右鍵點選特定的網路請求,並選擇"Override content"(覆蓋響應)。這將自動在開發者工具的 Sources 皮膚的 Overrides 部分生成一個本地的響應檔案。

image.png

可以在本地響應檔案自定義的響應內容,包括狀態碼、響應頭和響應體等,模擬不同業務場景的響應。

image.png

第三方Mock平臺

使用第三方的 Mock 平臺可以幫助你進行 API 的模擬和模擬資料的生成,下面以 Fast Mock 平臺為例:

在平臺配置了一個 Mock 介面 /user/address
image.png

然後配置代理:

// setupProxy.js
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
  // 匹配反向代理
  app.use(proxy([
    "/user/address",
  ], {
    target: 'https://www.fastmock.site/mock/6132bf44cf9f905257ca12ba9312ea10',
    changeOrigin: true,
  }));

  // 或者使用中介軟體匹配路由,307 重定向
  app.use((req, res, next) => {
    // 匹配路由
    if (/^\/user\/address$/.test(req.path)) {
      res.redirect(307, 'https://www.fastmock.site/mock/6132bf44cf9f905257ca12ba9312ea10/user/address');
    }
    next();
  });
};

image.png

相關文章