說到前端開發中的mock,即假數據,咱們都知道它是用來解決前端開發的閉環調試或測試用的一種技術手段,就是當真正的服務端api沒有開發完成,只有接口文檔的狀況下前端能夠先行完成功能開發和測試。mock方案既能夠放在服務端,也能夠放在本地。javascript
服務端的mock一般須要專門開啓一個mock服務,能夠進行接口配置和跨域等。前端
本地的mock方案則相對靈活不少,最簡單的方式是將mock數據直接注入接受請求返回值的變量中,可是它的缺點很大,侵入性太強,不夠靈活,甚至能夠說污染了業務邏輯,徹底是給後續挖坑的方案。稍微複雜一點的方案好比mock.js, 經過修改XmlHttpRequest
的默認行爲能夠攔截ajax,解決了構造隨機mock以及侵入太強的問題,可是它的缺點一樣明顯,真實連調時須要移除相關代碼,而且它對於fetch請求素手無策。vue
要作到業務代碼無侵入的mock,也很簡單。如今的前端項目都是工程化的,大部分都會用到一個本地node服務,好比webpack-dev-server
等,咱們在和服務端連調以前調試網頁,看到的全部內容都是本地服務提供的靜態響應,api完成以前即使使用代理,繞過跨域也沒法有效自測,咱們能夠從這個本地服務去入手,經過實現一箇中間件去攔截http請求,而後對API接口類型的請求作自定義的響應處理來實現mock。這樣的mock方案能夠作到業務代碼零侵入,連調時只要關閉mock的開關或者移除中間件就能夠。java
大多數前端項目本地服務都是依賴express,以它爲例,實現一箇中間件要如何作呢。這裏的關鍵問題在於URL如何去映射,有兩種方案:node
文件映射是指對請求url中的「/」替換,好比api/a/b
這樣一個請求,能夠映射成api_a_b
這樣命名的文件,中間件經過讀取文件來響應請求。react
map映射是指沒必要按照嚴格的格式去創建映射文件,而是經過配置一個json
文件去處理,key
表示url
,value
爲待返回的響應內容。webpack
文件映射的優勢是沒必要進行專門配置,對新請求只要創建新的映射文件就行了,可是缺點也很明顯,因爲文件名是靜態的,這種方式難以處理相似api/a/b/10
這樣帶有pathParam
的get
請求,還有就是文件沒法複用,即使兩個不一樣請求返回的結果是相同的,也須要分別創建各自的mock
文件。相比之下,map映射的方式除了必須的路由配置外,實現能夠至關靈活,能夠複用mock
。git
下面介紹一下本身寫的一箇中間件dynamic-mock-express,它具有以下功能和特性:github
1.能夠友好地支持Restful API
2.能夠自定義url過濾規則,肯定哪些請求不須要mock
3.支持將map映射的value設置爲函數,而且能夠經過參數接受相對應請求的query,params和body
4.支持將返回值的任意屬性或嵌套屬性設置爲函數,一樣能夠接受上述參數
5.配置無緩存,修改後不須要重啓啓動項目就能夠生效
6.函數經過接受store對象能夠實現動態的、響應式的mock,能夠簡單模擬真實後端服務
web
1.安裝:
npm i dynamic-mock-express -D
複製代碼
2.根目錄創建mock文件夾,在該文件夾下面創建index.js
,即配置文件,例如:
// index.js
module.exports = {
needMock: true, // 是否開啓mock,默認true,連調時設爲false 便可,沒必要重啓項目
prefix: "api", //須要mock的url前綴
tip: true, //爲true時匹配不到url時控制檯會警告,默認爲true
ignore: (url, method) => {
// 能夠接受url和請求的method,返回是true的將被過濾,不會被mock處理
},
routes: {
"GET:a/c": require("./mock_1"),
"GET:a/b/:id": (data) => { // 能夠接受params參數,data共有四個屬性:params、query、body、store
return {
data: "mock_2",
params: data.params,
};
},
"GET:b/:id/:code": ({params}) => { //將請求內容原樣返回
return {
id: params.id,
code: params.code
}
},
"POST:b/c": { // 能夠直接將value定義爲一個json對象
a:1
},
"POST:a/b/c": data => { // 經過data.body能夠將post數據自己做爲響應
return {
status: true,
body: data.body
};
},
"DELETE:a/b/:id": (data) => { // 支持Restful api
return {
id: data.params.id
};
},
"DELETE:a/b/c/:id": { // 支持屬性或嵌套屬性定義爲函數
a: {
b: 1,
c: (data) => {
return {
id: data.params.id
}
}
}
}
}
};
複製代碼
3.掛載中間件
const app = express();
const mock = require("dynamic-mock-express");
app.use(
mock({
mockDir: path.resolve(__dirname, "../mock")
})
);
複製代碼
const mock = require("dynamic-mock-express");
new WebpackDevServer(compiler, {
...
setup: (app) => {
app.use(
mock({
mockDir: path.resolve(__dirname, "../mock"), // mock文件夾目錄
entry: "index.js" // mock文件夾入口,若是配置文件就叫index.js,能夠不配置
})
);
}
}
複製代碼
假如發出一個api/a/b/10
的get
請求,mock將會按照如上配置響應以下結果:
{
data: "mock_2",
params: {
id: "10"
}
}
複製代碼
4.響應式的動態mock
配置storePath
屬性可讓mock
具有響應式能力,以下:
const path = require("path");
module.exports = {
needMock: true,
prefix: "api",
storePath: Path.reslove(__dirname, "store"), // 必須是絕對路徑
tip: true,
routes: {
"GET:a/b/:id": ({store, params}) => {
return store.data.find(item => {return item.id == params.id});
},
"POST:a/b": ({store, body}) => {
store.data.find(item => {
return item.id == body.id
}).name = body.name;
return {
status: true
};
}
}
}
複製代碼
mock/store.js
module.exports = {
data: [
{
id: 10,
name: "zhangsan"
},
{
id: 11,
name: "lisi"
}
]
}
複製代碼
用僞代碼的形式去發起以下請求:
// pseudocode
get("api/a/b/10").then(res => {
console.log(res) // {id: 10, name: "zhangsan"}
post("api/a/b")
.send({id: 10, name: "wangwu"})
.then(res => {
get("api/a/b/10").then(res =>{
console.log(res) // {id: 10, name: "wangwu"}
})
})
})
複製代碼
能夠發現post修改數據後再次get的時候結果已經更新,這樣咱們就能夠經過簡單的一些邏輯代碼模擬一些調用真實api接口的交互而不用手動去修改mock。
項目地址github.com/silentport/…,歡迎你們提issue。