前端開發如何做好本地接口模擬
之前有寫過一篇 本地化接口模擬、前後端並行開發,講到過本地接口模擬,但不太細緻。這次細細的說說本地接口模擬。
1. 有什麼好處
本地接口模擬最大的好處就是能夠使前後端項目解耦,前端更專注於開發,減少線上調試,以此提升開發效率。
2. 有哪些途徑
本地接口模擬一般分爲工具層面和代碼層面。
3. 工具層面
就工具層面而言,一般是由項目的構建工具提供的功能。比如,當我們用 webpack-dev-server、webpack-dev-middleware + browser-sync 等工具時,就可以向工具裏添加本地接口模擬功能。
注:這裏不講解工具如webpack-dev-server
、webpack-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
文件,其他的如 html
、js
、css
之類的文本文件、如圖片之類的二進制文件也可以訪問。另外,只要文件有更新,刷新瀏覽器頁面就可以重新獲取新的文件,沒有緩存。
因爲本地接口模擬功能主要是針對的返回值爲 json
格式的異步請求,所以這種方式主要用 json
文件。
這種方式是最簡單、快捷、使用難度最低的方式。
3.2 動態註冊接口
使用靜態文件做本地接口模擬功能主要存在以下的一些問題:
- 靜態文件只能以
get
方法訪問 - 輸入數據是靜態的,不能做運算、循環、判斷等,也不能根據請求參數做出不同的響應
- 本地接口名與服務器上的接口名不一樣,這就比較麻煩了,每次上線到服務器的時候都得改接口名
所以,多數情況下,都會採用動態註冊接口的方式做本地接口模擬功能。
這種方式是用 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 }],
}),
};
注:
- 上面的寫法只是示例,可以自定規範、書寫格式等
- 可以用 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 操作界面,可以添加多個用戶、多個團隊、多個倉庫,可以生成爲每一個請求參數添加類型限定、描述,爲響應數據字段添加描述等。
因爲是在線上的模擬數據,所以在任何地方都可用,不管是本地開發,還是線上調試、演示,都是可用的。
比較有名的線上接口模擬工具有:
- easy-mock:線上演示地址 https://easy-mock.com/
- RAP / rap2-delos + rap2-dolores:阿里出品,線上演示地址 http://rap2.taobao.org/
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-delos:後端數據 API 服務器
- rap2-dolores:前端靜態資源
RAP2 的安裝步驟要麻煩一些,rap2-delos
可以參考官方文檔 rap2-delos#部署、非官方rap2-delos部署文檔,rap2-dolores
可以參考官方文檔 rap2-dolores#deployment-部署。
RAP2 提供了與 easy-mock
類似的功能,但比 easy-mock
要更強大一些,當然也要複雜一些,比如:
- 對請求參數支持更完備,比如
headers
、query params
、body params
- 對響應數據支持更完備,比如可以爲每個字段添加描述、限定類型等
3.4.3 兩者之間比較
RAP2 比 easy-mock
要更強大一些,但也要複雜一些,所以追求功能完備的可以用 RAP2,追求簡單快捷的可以用 easy-mock
。
4. 代碼層面
從上面可以看出,除了第二種方式 動態註冊接口
之外,其他的方式都不能做到完全模擬服務器環境,至少服務器接口地址與本地模擬地址不一樣,這就有一個問題:每次上線前都得改成服務器地址。
另外,前端與後端對接的過程中也總是難免會遇到一些問題:
- 前端傳的參數與後端所需的參數難以保持一致:比如分頁,前端定的
page
、從 1 開始,後端定的pageNum
、從 0 開始 - 前端需要的數據與後端返回的數據相差很大,需要對返回的數據做處理
- 後端可能更改字段名或者數據類型,導致前端要查找、並更新相應的代碼
一種好的、解決這些問題的方式是對應用進行分層、把異步請求進行隔離封裝。
我一般會用 see-fetch、see-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
版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證)