前端開發如何做好本地接口模擬

前端開發如何做好本地接口模擬

之前有寫過一篇 本地化接口模擬、前後端並行開發,講到過本地接口模擬,但不太細緻。這次細細的說說本地接口模擬。

1. 有什麼好處

本地接口模擬最大的好處就是能夠使前後端項目解耦,前端更專注於開發,減少線上調試,以此提升開發效率。

2. 有哪些途徑

本地接口模擬一般分爲工具層面和代碼層面。

3. 工具層面

就工具層面而言,一般是由項目的構建工具提供的功能。比如,當我們用 webpack-dev-serverwebpack-dev-middleware + browser-sync 等工具時,就可以向工具裏添加本地接口模擬功能。

注:這裏不講解工具如 webpack-dev-serverwebpack-dev-middleware + browser-sync 等的用法,如有需要,可以自己去了解一下

下面以 webpack-dev-server 爲例進行講解,其他工具類似。

3.1 靜態文件

最簡單的,我們可以用靜態 json 文件做本地接口模擬功能。

|-- /                        # 項目根目錄
  |-- mock/                  # 模擬數據目錄(可以自定義)
    |-- 1.json
    |-- 2.json
    |-- ...
    
  |-- ...                    # 其他文件                 

然後用 webpack-dev-server 以項目根目錄爲基地址來開啓本地開發調試,在頁面中就可以這樣訪問:

fetch('/mock/1.json');       // 訪問 1.json 
fetch('/mock/2.json');       // 訪問 2.json

# 可以將 fetch 換成其他請求方式 

這種方式可以訪問項目中所有的文件,不光是 json 文件,其他的如 htmljscss 之類的文本文件、如圖片之類的二進制文件也可以訪問。另外,只要文件有更新,刷新瀏覽器頁面就可以重新獲取新的文件,沒有緩存。

因爲本地接口模擬功能主要是針對的返回值爲 json 格式的異步請求,所以這種方式主要用 json 文件。

這種方式是最簡單、快捷、使用難度最低的方式。

3.2 動態註冊接口

使用靜態文件做本地接口模擬功能主要存在以下的一些問題:

  1. 靜態文件只能以 get 方法訪問
  2. 輸入數據是靜態的,不能做運算、循環、判斷等,也不能根據請求參數做出不同的響應
  3. 本地接口名與服務器上的接口名不一樣,這就比較麻煩了,每次上線到服務器的時候都得改接口名

所以,多數情況下,都會採用動態註冊接口的方式做本地接口模擬功能。

這種方式是用 js 文件編寫一系列的 路由 => 響應 映射,然後動態的把定義好的接口註冊到工具實例中。

目錄結構:

|-- /                        # 項目根目錄
  |-- mock/                  # 模擬數據目錄(可以自定義)
    |-- user.js
    |-- home.js
    |-- ...
    
  |-- ...                    # 其他文件

示例 mock 文件的寫法(可以自定規範,下面只是演示):

# mock/user.js

module.exports = {
  'GET /user/profile': { ... },                  // 直接返回一個對象
  'POST /user/update': (req, res) => { ... },    // 根據 `req, res` 的自定義響應
  ...
};


# mock/home.js

const mockjs = require('mockjs');

module.exports = {
  'GET /home/list': mockjs.mock({                // 用 mockjs 輔助生成假數據
    'list|1-10': [{ 'id|+1': 1 }],
  }),
};

注:

  1. 上面的寫法只是示例,可以自定規範、書寫格式等
  2. 可以用 mockjs 庫來幫助生成假數據

webpack-dev-server 的相關配置:

# webpack.config.js

const beforeDevServer = app => {

  // 在這裏讀取 mock 目錄下的所有文件,按照一定的規範和格式,載入動態接口
  // 比如:
  
  app.get('/user/profile', function(req, res) {
    res.json({ ... });                           // 返回文件中定義的 `GET /user/profile` 的值
  });
  
  app.post('/user/update', function(req, res) {
    handle(req, res);                            // handle:文件中定義的 `POST /user/update` 的自定義處理函數
  });
  
  ...
};

module.exports = {
  //...
  devServer: {
    before: beforeDevServer,
  }
};

然後用 webpack-dev-server 開啓本地開發調試,在頁面中就可以這樣訪問:

fetch('/user/profile');                          // 訪問 /user/profile 
fetch('/user/update', {method: 'post', body: { ... }});
                                                 // 訪問 /user/update
fetch('/home/list');                             // 訪問 /home/list

# 可以將 fetch 換成其他請求方式 

一般來說,我們還會用上 chokidar 來監聽 mock 目錄下的文件變動,來更新路由及其響應,以此能夠做到每次訪問到的都是最新的資源(因爲 node 針對某個模塊只會加載一次)。

const chokidar = require('chokidar');

const watcher = chokidar.watch('./mock');
watcher.on('change', path => {

  // 先清除模塊緩存,保證加載最新的資源
  if (require.cache[path]) delete require.cache[path];
  
  const mapObj = require(path);
  
  // 接下來把映射對象 mapObj 重新映射到 app 中
  ...
});

這種方式比較複雜,尤其是對項目搭建者要求比較高,需要對相關工具有深入的瞭解,好在社區已經有封裝好的工具:roadhog

但這種方式對使用者是很棒的,因爲能夠完全模擬服務器接口,包括接口名、HTTP 方法、參數、返回值等,所以同樣的代碼既可以在本地運行,也可以在服務器上運行。

所以,這也是比較推薦的方式。

注:上面的代碼只是演示構建過程,並不保證可以運行

3.3 使用代理

這種方式是把本地模擬文件寫在另一個單獨項目裏,然後使用使用代理的方式,訪問模擬接口。

mock 項目(以 koa 爲例):

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

router.get('/api/user/profile', (ctx, next) => {
  ctx.body = { ... };
});

router.post('/api/user/update', (ctx, next) => {
  // ...
});

app
  .use(router.routes())
  .use(router.allowedMethods());

app.listen(3000);    

app 應用項目:

webpack-dev-server 的相關配置:

# webpack.config.js

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:3000'            // 把所有 `/api` 開頭的接口都代理到 `mock` 項目中 
    }
  }
};

然後用 webpack-dev-server 開啓本地開發調試,在頁面中就可以這樣訪問:

fetch('/api/user/profile');                      // 訪問 mock 項目的 /api/user/profile 
fetch('/api/user/update', {method: 'post', body: { ... }});
                                                 // 訪問 mock 項目的 /api/user/update
                                                 
# 可以將 fetch 換成其他請求方式 

一般來說,我們還會用上 nodemon 來監聽 mock 項目中的文件變動,自動重啓 mock 應用程序,以此能夠做到每次訪問到的都是最新的資源(因爲 node 針對某個模塊只會加載一次)。

nodemon app.js

這種方式可以統一管理多個項目的數據模擬文件,多個項目可以共享一些模擬數據。

3.4 線上接口模擬

有些時候,當我們在產品環境的時候(在線上)也可能想用模擬數據(比如APP、微信小程序、用於演示的 web 應用等),或者需要一個線上的地方來統一管理模擬數據時,就需要線上接口模擬了。

線上接口模擬擁有完備的 UI 操作界面,可以添加多個用戶、多個團隊、多個倉庫,可以生成爲每一個請求參數添加類型限定、描述,爲響應數據字段添加描述等。

因爲是在線上的模擬數據,所以在任何地方都可用,不管是本地開發,還是線上調試、演示,都是可用的。

比較有名的線上接口模擬工具有:

3.4.1 easy-mock

圖片描述

環境需求:Node.js (>= v8.9) & MongoDB (>= v3.4) & Redis(>= v4.0)

安裝步驟請參考官方的文檔 easy-mock#quick-start

easy-mock 主要提供了以下的一些功能:

  • 支持接口代理
  • 支持快捷鍵操作
  • 支持協同編輯
  • 支持團隊項目
  • 支持 RESTful
  • 支持 Swagger | OpenAPI Specification (1.2 & 2.0 & 3.0)

    • 基於 Swagger 快速創建項目
    • 支持顯示接口入參與返回值
    • 支持顯示實體類
  • 支持靈活性與擴展性更高的響應式數據開發
  • 支持自定義響應配置(例:status/headers/cookies)
  • 支持 Mock.js 語法
  • 支持 restc 方式的接口預覽

3.4.2 RAP / rap2-delos + rap2-dolores

圖片描述

環境需求:Node.js (>= v8.9) & MySQL (>= v5.7) & Redis(>= v4.0)

RAP 目前有兩個版本,第一個版本的 RAP 已經被官方廢棄了,建議用第二個版本。

RAP2 分成了兩個包:

RAP2 的安裝步驟要麻煩一些,rap2-delos 可以參考官方文檔 rap2-delos#部署非官方rap2-delos部署文檔rap2-dolores 可以參考官方文檔 rap2-dolores#deployment-部署

RAP2 提供了與 easy-mock 類似的功能,但比 easy-mock 要更強大一些,當然也要複雜一些,比如:

  • 對請求參數支持更完備,比如 headersquery paramsbody params
  • 對響應數據支持更完備,比如可以爲每個字段添加描述、限定類型等

3.4.3 兩者之間比較

RAP2 比 easy-mock 要更強大一些,但也要複雜一些,所以追求功能完備的可以用 RAP2,追求簡單快捷的可以用 easy-mock

4. 代碼層面

從上面可以看出,除了第二種方式 動態註冊接口 之外,其他的方式都不能做到完全模擬服務器環境,至少服務器接口地址與本地模擬地址不一樣,這就有一個問題:每次上線前都得改成服務器地址。

另外,前端與後端對接的過程中也總是難免會遇到一些問題:

  • 前端傳的參數與後端所需的參數難以保持一致:比如分頁,前端定的 page、從 1 開始,後端定的 pageNum、從 0 開始
  • 前端需要的數據與後端返回的數據相差很大,需要對返回的數據做處理
  • 後端可能更改字段名或者數據類型,導致前端要查找、並更新相應的代碼

一種好的、解決這些問題的方式是對應用進行分層、把異步請求進行隔離封裝。

我一般會用 see-fetchsee-ajax 對異步請求進行隔離封裝。

注:see-fetch 是對 window.fetch 的封裝,see-ajax 是對 XMLHttpRequest 對象的封裝。

4.1 以 see-fetch 爲例進行說明:

可以在代碼中設置多個內部環境,然後針對不同的外部環境設置不同的內部環境(如:本地環境、線上環境等),這樣就可以做到不改代碼,只改一個環境值。

如果搭配 define-plugin,連環境值都不需要改,直接由 define-plugin 在運行的過程中指定。

import seeFetch from 'see-fetch';

seeFetch.setEnv(0/1/2/3);

seeFetch.setEnv(__SEE_ENV__);          // __SEE_ENV__ 由 define-plugin 運行中指定

配置一個異步請求:

seeFetch.config(name, {                // 定義一個名爲 name 的異步請求(下面的配置可以是多環境,每個環境可以設置不同的值)
  method,                              // 當前請求使用什麼 http 方法
  stringify,
  settings,
  url,                                 // 當前請求 url 地址
  req,                                 // 請求參數鍵名的映射,比如 `page => pageNum`
  pre,                                 // 操作請求參數,比如 page 從 1 開始改成從 0 開始
  refactor,                            // 重構響應數據,如字段重命名、類型轉換等
  post,                                // 操作響應數據,以把數據轉換成自己所需要的數據
  implement,                           // 自定義請求,比如後端返回一個模板字符串,而不是接口
});

發起訪問:

seeFetch(name, params).then(result => {
  // 這裏的 result 是經過格式化後的最終數據 
});

從上面可以看出:一個請求的不確定性都被封裝到了配置中,不管是接口地址更新、前端請求參數與後端不一致、後端響應數據與前端所需的差異很大等,都可以在配置中進行操作,而絲毫不需要改其他地方的代碼。

這樣,如果後端接口有什麼改動的,只需要找到配置文件進行更新,而不用在項目中找哪裏使用了這個接口。

如此,既能很好的使用本地數據模擬,也可以從容應對後端接口的改動,便能事半功倍。

後續

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章