具体需求开发前,后端往往只提供接口文档,对于前端,最简单的方式就是把想要的数据写死在代码里进行开发,但这样的坏处就是和后端联调前还需要再把写死的数据从代码里删除,最好的方式是无侵入的 mock
。下边介绍几种常用的方式,大家可以结合自己的项目来选取。
大致分为三类,重写 xhr/fetch
、node.js
服务中转、系统层面拦截。
接口demo
为了后边方便的安装 node
包,可以用 webpack
进行打包,具体配置可以参考 2021年从零开发前端项目指南,看到 React
配置的前一步就够了,只需要配置一个 html
和一个接口请求。 需要注意下 webpack
的版本,不同版本后续的配置会不同,这里我用的是 5.75.0
。
最终目标是通过 mock
让下边还没有开发好的接口正常返回数据:
1 2 3 4 5 6
| fetch("/api/data", { body: JSON.stringify({ id: 10 }), method: "POST", }) .then((response) => response.json()) .then((json) => console.log(json));
|
现在肯定是 404
。
Better-mock
better-mock fork
自 Mock.js,使用方法和 Mock.js
一致,用于 javascript
mock
数据生成,它可以拦截 XHR
和 fetch
请求,并返回自定义的数据类型。
只需要在调用接口前,引入 better-mock
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Mock from "better-mock";
Mock.mock("/api/data", { "list|1-10": [ { "id|+1": 1, }, ], });
fetch("/api/data", { body: JSON.stringify({ id: 10 }), method: "POST", }) .then((response) => response.json()) .then((json) => console.log(json));
|
控制台此时就会输出数据了。
better-mock
一个好处就是可以通过它既有的语法来生成一些随机的数据,每次请求都会返回不同的数据。
坏处是会在请求发送前就拦截,导致在 Chrome
控制台就看不见请求了。
just mock
just mock 是一个浏览器插件,在代码中什么都不需要更改,只需要添加相应的接口和数据即可实现拦截。
插件安装好后添加相应的域名就可以拦截到相应的请求。
接着进行相应的编辑添加对应的 mock
数据就好。
这样接口就会被拦截,控制台输出预设的数据:
浏览器插件原理和 Better-mock
是一样的,但会更加轻便,无需融入到代码中。两者的原理是一样的,都是在网络请求前重写了全局的 xhr
和 fetch
,具体可以参考 油猴脚本重写fetch和xhr请求。
koa
本地通过 koa
开启一个接口服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const Koa = require("koa"); const router = require("koa-router")(); const app = new Koa();
router.post("/api/data", async (ctx, next) => { ctx.response.body = { status: true, data: [1, 2, 3], msg: "获取数据成功", }; });
app.use(router.routes());
app.listen(3000);
|
本地开启运行:node server.js
,接口提供的地址是 localhost:3000
,但是请求的地址是 loacalhost:8080
,当然可以直接修改代码里的地址为 localhost:3000
,但还可以通过 webpack
的配置,将请求转发到 localhost:3000
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "./dist"), filename: "bundle.js", }, devServer: { static: path.resolve(__dirname, "./dist"), proxy: { "/api": { target: "http://localhost:3000", secure: false, }, }, }, };
|
这样就可以看到控制台输出了:
此外,Chrome
的 Network
也可以正常看到这个请求:
这种方法也可以用来解决跨域问题,举个例子:
如果本地想访问一个具体域名的接口,比如请求知乎的热榜接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| fetch( "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50&desktop=true", { headers: { accept: "*/*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "sec-ch-ua": '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"macOS"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "x-ab-param": "", "x-ab-pb": "CpoBCAAbAD8ARwC0AGkBagF0ATsCzALXAtgCoAOhA6IDtwOmBNYEEQVRBYsFjAWeBTAGMQbrBicHdAh2CHkI2gg/CWAJwwnECcUJxgnHCcgJyQnKCcsJzAnRCfQJBApJCmUKawqYCqUKqQq+CsQK1ArdCu0K/go7CzwLQwtGC3ELhwuNC9cL4AvlC+YLLAw4DHEMjwysDMMMyQz4DBJNAQAAAAAAAAAAAAAAAAAAAAAEAAEAAAEAAAEAAAIGAAABAAAAAAAAAAAAAAADAAAAAAEAAAABAQAAAAEAAQAAAAUCAQAABgIEAAACAAA=", "x-api-version": "3.0.76", "x-requested-with": "fetch", "x-zse-93": "101_3_3.0", "x-zse-96": "2.0_LYJSVCX+9b1YXp/sG1Azyi5tC5RpabLbkXb3w5s6rv=Gxy9uMXqMXm4LjYWRdoIz", "x-zst-81": "3_2.0aR_sn77yn6O92wOB8hPZnQr0EMYxc4f18wNBUgpTQ6nxERFZfRY0-4Lm-h3_tufIwJS8gcxTgJS_AuPZNcXCTwxI78YxEM20s4PGDwN8gGcYAupMWufIoLVqr4gxrRPOI0cY7HL8qun9g93mFukyigcmebS_FwOYPRP0E4rZUrN9DDom3hnynAUMnAVPF_PhaueTF4C8IhwVIDO_8ioC0JXfW9CKpCwCs4OBQAc0uBefagCKGMo1yroBh9CKe_STVHC1IqLKHJL_chSflqHCOqgYPhYKVwH8M4Lqqq9y1wH967NC7vH80UC8wCHswgHBDgY_ovg9r0wBcJO8s9OCzcLMNgLfkgNByqCLhhUf_veOQRY_dvxmCg_zugS8iBtBFgOZkwNLDw2skTX18XSYuJLqpCYBo_pMWbS8Pv3YtGFBaqL9AwCYhbL9eGVV2rNClDL1wJLmxCgKagNBUwSqYrHBbGp8e8HGggSMQ7xC3rOs", }, referrer: "https://www.zhihu.com/hot", referrerPolicy: "no-referrer-when-downgrade", body: null, method: "GET", mode: "cors", credentials: "include", } );
|
由于本地域名是 http://localhost:8080/
,此时浏览器就会报跨域的错了。
此时后端可以通过 CORS
策略解决跨域的问题,但因为是测试环境,后端可能会说你自己解决吧,此时就可以通过 Koa
进行中转。
改写一下 Koa
的代码,先请求后端的接口,接着将收到的数据拿到后返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import Koa from "koa"; import fetch from "node-fetch"; import Router from "koa-router";
const app = new Koa(); const router = new Router(); router.post("/api/data", async (ctx, next) => { const res = await fetch( "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50&desktop=true", { headers: { accept: "*/*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "sec-ch-ua": '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"macOS"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "x-ab-param": "", "x-ab-pb": "CpoBCAAbAD8ARwC0AGkBagF0ATsCzALXAtgCoAOhA6IDtwOmBNYEEQVRBYsFjAWeBTAGMQbrBicHdAh2CHkI2gg/CWAJwwnECcUJxgnHCcgJyQnKCcsJzAnRCfQJBApJCmUKawqYCqUKqQq+CsQK1ArdCu0K/go7CzwLQwtGC3ELhwuNC9cL4AvlC+YLLAw4DHEMjwysDMMMyQz4DBJNAQAAAAAAAAAAAAAAAAAAAAAEAAEAAAEAAAEAAAIGAAABAAAAAAAAAAAAAAADAAAAAAEAAAABAQAAAAEAAQAAAAUCAQAABgIEAAACAAA=", "x-api-version": "3.0.76", "x-requested-with": "fetch", "x-zse-93": "101_3_3.0", "x-zse-96": "2.0_LYJSVCX+9b1YXp/sG1Azyi5tC5RpabLbkXb3w5s6rv=Gxy9uMXqMXm4LjYWRdoIz", "x-zst-81": "3_2.0aR_sn77yn6O92wOB8hPZnQr0EMYxc4f18wNBUgpTQ6nxERFZfRY0-4Lm-h3_tufIwJS8gcxTgJS_AuPZNcXCTwxI78YxEM20s4PGDwN8gGcYAupMWufIoLVqr4gxrRPOI0cY7HL8qun9g93mFukyigcmebS_FwOYPRP0E4rZUrN9DDom3hnynAUMnAVPF_PhaueTF4C8IhwVIDO_8ioC0JXfW9CKpCwCs4OBQAc0uBefagCKGMo1yroBh9CKe_STVHC1IqLKHJL_chSflqHCOqgYPhYKVwH8M4Lqqq9y1wH967NC7vH80UC8wCHswgHBDgY_ovg9r0wBcJO8s9OCzcLMNgLfkgNByqCLhhUf_veOQRY_dvxmCg_zugS8iBtBFgOZkwNLDw2skTX18XSYuJLqpCYBo_pMWbS8Pv3YtGFBaqL9AwCYhbL9eGVV2rNClDL1wJLmxCgKagNBUwSqYrHBbGp8e8HGggSMQ7xC3rOs", }, referrer: "https://www.zhihu.com/hot", referrerPolicy: "no-referrer-when-downgrade", body: null, method: "GET", mode: "cors", credentials: "include", } ); const data = await res.json(); ctx.response.body = { status: true, data, msg: "获取数据成功", }; }); app.use(router.routes());
app.listen(3000);
|
此时还是请求 /api/data
。
1 2 3 4 5 6
| fetch("/api/data", { body: JSON.stringify({ id: 10 }), method: "POST", }) .then((response) => response.json()) .then((json) => console.log(json));
|
依旧让 Webpack
将数据转发到 Koa
。
1 2 3 4 5 6 7 8 9 10
| devServer: { static: path.resolve(__dirname, "./dist"), proxy: { "/api": { target: "http://localhost:3000", secure: false, }, }, },
|
现在控制台输出的就是知乎返回的数据了,跨域问题也消失了:
当然上边解决跨域只是一个思路,具体的封装还需要结合项目来进行。
webpack
上边可以通过 webpack
进行转发数据,是因为 webpack
也启动了一个 HTTP
服务器,只不过用的不是 Koa
,是更早的一个框架 Express
,而且它们是同一个团队开发的。
既然已经有了一个 HTTP
服务器,所以也没必要再开启另一个 Koa
的了,通过给 webpack
传递一个函数,重写 Koa
返回的数据即可。
只需要通过 setupMiddlewares
重写数据即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "./dist"), filename: "bundle.js", }, devServer: { static: path.resolve(__dirname, "./dist"), setupMiddlewares: (middlewares, devServer) => { if (!devServer) { throw new Error("webpack-dev-server is not defined"); }
middlewares.unshift({ path: "/api/data", middleware: (req, res) => { res.send({ list: [1, 2, 3], msg: "webpack mock" }); }, });
return middlewares; }, }, };
|
此时控制台也可以看到输出的内容:
同时 Network
也是可以看到网络请求的。
Charles
终极必杀 mock
方法,因为它除了可以拦截浏览器中的请求,也可以拦截任意 App
的数据,甚至还可以拦截手机中的 HTTPS
请求,前段时间很火的羊了个羊就可以通过 Charles
抓取请求然后迅速通关。
需要注意的是 Charles
抓不到 localhost
的请求,访问的时候需要将 localhost
改为 localhost.charlesproxy.com
。
webpack
需要加一个 allowedHosts
的配置,不然会返回 Invalid Host header
。
1 2 3 4
| devServer: { static: path.resolve(__dirname, "./dist"), allowedHosts: "all", },
|
全部配置好后就可以看到 Charles
抓到的请求了。
此时只需要提前写好一个 json
文件,然后将右键选择 Map Local
对应的文件即可。
1 2 3 4
| { "data": [1, 2, 3], "msg": "from charles" }
|
接下来就可以在控制台看到 mock
成功了。
总
几种 mock
方式各有优缺点,上边只是提供一个思路,具体的 mock
方案就需要结合项目进行一定的封装了。