前言
一个前后端分离的项目,前端人员需要对接后端的接口。如果在后端的接口没有开发好,或者没有测试版可以对接的情况下,前端人员也不能坐等后端接口写好后再开始开发。
一个项目的,理想情况下接口的规范应该是前后端人员在开发之前就已经协商好的,如请求内容,返回内容等。在后端接口还不能供前端人员使用的时候,前端就可以通过名为 mock 的技术,伪造接口。
核心思想就是:在开发过程中,通过 mock 来拦截发起的请求,并返回伪造的数据。
在这里我们将使用 msw
包来实现。
MSW
msw 是个 mock 工具,官网在这。用于提供 mock 功能。
原理
假设我们现在有个按钮,点击后将发起请求,向服务器获取一些数据,显示到页面上。那么当前的后端接口尚未可用,我们就可以在请求后伪造一些数据,这样看起来就像请求真实接口一样。其原理如下:
在本文中使用的示例代码,我放到了 codesandbox 上:try to use mocks in react
使用
在你的项目根目录下打开终端,输入 npm install msw --save-dev
安装 msw。参数 --save-dev
是必须的,因为只在开发时使用 mock,所以要安装到 devDependencies 下。
接下来我们要告诉 msw,哪些请求我们是要拦截的。
在 src 文件夹下新建一个 mocks 文件夹,我们有关 mock 的代码都会写在这个文件夹里。我们在这个文件夹里新建一个名为 handlers.ts
的文件,在这个文件里我们将告诉 msw 要 mock 哪些接口。
// src/mocks/handlers.js
import { rest } from "msw";
export const handlers = [
// Handles a POST /login request
rest.post("/login", null),
// Handles a GET /user request
rest.get("/user", null),
];
在上面的示例代码中,我们首先从 msw 中导入了 rest
模块,关于 RESTAPI 规范的 mock 功能都在这个 rest
模块里。可以使用 rest.[Method]
语法来定义你的 mock 请求,如示例中使用 rest.post(...)
来 mock 了一个 post 请求。这个方法需要两个参数,第一个参数 mock 的 api 地址,第二个参数是一个返回假数据的函数。
上面的示例中,我们定义了两个 mock,且返回假数据的函数是 null。为了方便演示,我们只留一个。
// src/mocks/handlers.js
import { rest } from "msw";
export const handlers = [
rest.get("/getSth", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
username: "admin",
})
);
}),
];
在这里我们给第二个参数传入了函数,看该函数的三个签名的含义:
- req:表示请求信息
- res:用于构造假响应的方法
- ctx:用于设置相应数据的功能类
我们看上面的代码,我们定义了一个 get 请求的 mock,api 路由为 /getSth
,使用 res
函数返回假数据,使用 ctx.status(200)
告诉 res
假响应的状态码是 200,使用 ctx.json
告诉 res
假相应的内容是 json 格式,并且传入了响应内容。
我们将 mock 放入了一个名为 handlers
的数组,记得 export
导出,需要在外部用到。
浏览器拦截
我们已经写好了 mock 的数据,接下来要拦截浏览器的请求。幸运的是,msw 帮我们做好了大部分的工作,我们只需要在终端里执行 npx msw init <PUBLIC_DIR> --save
,这个命令里,要把 <PUBLIC_DIR>
替换为你项目的公开目录,我们的项目是使用 vite
构建的,它的公开目录就是 ./public
。后面的参数 --save
将我们指定的公开目录保存到 package.json 里。
在我们使用 vite
构建的 react-ts 项目里,完整的命令是 npx msw init public/ --save
。
执行后,你将会看到这个文件:./publicmockServiceWorker.js
。文件内的代码不需要理会,我们只需要知道它拦截了浏览器的请求即可。
响应 mock
浏览器的拦截有了,接下来就是在拦截后,将 mock 的响应返回给浏览器。
在 src/mocks/
文件夹下新建文件 browser.ts
,写入如下代码:
import { setupWorker } from "msw";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
我们导入了 setupWorker
,这个方法用于设置 mock,你可以看到我们导入了 handlers
,先前写的 mock 就在这里,将它解构传入 setupWorker
,返回了一个 worker
。我们将使用 worker
来启动 mock 服务。
启动
我们要在项目启动时启动 mock 服务,所以回到我们的启动文件里,如果你用的是 vite
,那么启动文件应该是 main
;如果你用的是我的 codesandbox 连接,那么启动项目应该是 index.tsx
。我以 main
为例子,如下:
import { worker } from "./mocks/browser";
if (process.env.NODE_ENV === "development") {
worker.start();
}
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
ReactDOM.createRoot
这一句之前启动 mock。导入 browser.ts
中的 worker
,接着 worker.start()
启动即可。但是我们要注意,只需要在开发中启动 mock,所以要先判断当前环境,如果是开发环境才能启动。对应的代码就是:
if (process.env.NODE_ENV === "development") {
worker.start();
}
到这里,简单的 mock 就已经可以使用了,我们在开发中启动项目时就会顺道启动 mock 服务,如果启动 mock 成功,你会在浏览器控制台中看到:
[MSW] Mocking enabled.
测试一下
直接写一个按钮,点击后发起请求就可以了。
import { useState } from "react";
export default function App() {
const [result, setResult] = useState("");
async function handleRequest() {
const res = await fetch("/getSth").then((resp) => {
return resp.json();
});
setResult(res);
}
return (
<div className="App">
<button onClick={handleRequest}>发送请求</button>
<div>请求结果:</div>
<div>{result}</div>
</div>
);
}
如上,我们发起请求后,预期中会被 mock 拦截,返回 mock 的数据。由于我们没有真实的接口,所以如果 mock 启动失败,那么请求是会报错。
在 f12 中查看请求结果如下:
成功相应了 mock 中的数据。