一、引言
前面已经搭建了项目的基本机构,由于没有后端服务,暂且使用mock方案来完成动态路由的生成
现在前后端分离已成为主流架构模式。前端开发常常面临后端接口未完成或不稳定的情况,Mock 技术应运而生。Mock Service Worker(MSW)作为新一代的 Mock 方案,凭借其基于 Service Worker 的拦截能力,实现了对网络请求的无侵入式模拟,极大提升了前端开发与测试的效率。
二、MSW 原理与优势
Mock Service Worker是一种基于Service Worker API的网络请求拦截工具,它能够在不修改应用代码的前提下,拦截并模拟网络请求。MSW的工作原理可以概括为以下几个步骤:
- 注册Service Worker :MSW在应用启动时注册一个Service Worker,该Worker负责拦截网络请求。
- 请求拦截 :当应用发起网络请求时,已注册的Service Worker会拦截这些请求。
- 请求处理 :根据预定义的处理程序(handlers),Service Worker决定如何响应这些请求。
- 返回模拟数据 :Service Worker生成模拟响应并返回给应用
三、项目中的 MSW 集成
-
依赖安装于基础配置
npm insall msw axios -
msw初始化 快速将 ./mockServiceWorker.js 工作脚本复制到应用程序的公共目录中。该脚本负责拦截与分发请求,必须生成
//npx msw init <PUBLIC_DIR> --save npx msw init public --save -
axios简单封装
/** * axios请求封装 * 支持泛型返回、统一错误处理、自定义headers、loading控制等功能 */ import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios' import axios from 'axios' import { ElMessage, ElLoading } from 'element-plus' import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading' /** * 接口响应通用格式 * @template T 响应数据类型 */ export interface ApiResponse<T = unknown> { /** 响应数据 */ data: T /** 错误码,0表示成功,其他表示失败 */ errorCode: number /** 错误信息 */ errorMsg: string } /** * 扩展的请求配置 */ export interface RequestOptions extends AxiosRequestConfig { /** 是否显示loading,默认false */ showLoading?: boolean /** 是否直接返回data,默认true */ returnData?: boolean /** 自定义headers */ customHeaders?: Record<string, string> } /** * 默认配置 */ const defaultConfig: RequestOptions = { timeout: 10000, headers: { 'Content-Type': 'application/json', }, showLoading: false, returnData: true, } /** * 获取系统版本号 */ const getSystemVersion = (): string => { return import.meta.env.VITE_APP_VERSION || '1.0.0' } /** * 请求类 */ class Request { private instance: AxiosInstance private loadingInstance: LoadingInstance | null = null constructor(config: RequestOptions) { // 创建axios实例 this.instance = axios.create(config) // 请求拦截器 this.instance.interceptors.request.use( (config) => { const requestOptions = config as RequestOptions // 添加系统版本号到header config.headers['X-System-Version'] = getSystemVersion() // 添加自定义headers if (requestOptions.customHeaders) { Object.keys(requestOptions.customHeaders).forEach((key) => { config.headers[key] = requestOptions.customHeaders![key] }) } // 显示loading if (requestOptions.showLoading) { this.loadingInstance = ElLoading.service({ lock: true, text: '加载中...', background: 'rgba(0, 0, 0, 0.7)', }) } return config }, (error: unknown) => { return Promise.reject(error) }, ) // 响应拦截器 this.instance.interceptors.response.use( (response) => { // 关闭loading if (this.loadingInstance) { this.loadingInstance.close() } const { data } = response const requestOptions = response.config as RequestOptions // 判断是否成功 if (data.errorCode !== 0) { // 显示错误信息 ElMessage.error(data.errorMsg || '请求失败') return Promise.reject(data) } // 根据配置返回data或完整响应 return requestOptions.returnData ? data.data : data }, (error: unknown) => { // 关闭loading if (this.loadingInstance) { this.loadingInstance.close() } // 处理错误 let message = '网络请求失败' const axiosError = error as AxiosError if (axiosError.response) { switch (axiosError.response.status) { case 401: message = '未授权,请重新登录' break case 403: message = '拒绝访问' break case 404: message = '请求地址错误' break case 500: message = '服务器内部错误' break default: message = `请求失败(${axiosError.response.status})` } } else if (axiosError.message && axiosError.message.includes('timeout')) { message = '请求超时' } ElMessage.error(message) return Promise.reject(error) }, ) } /** * 发送请求 * @param config 请求配置 * @returns Promise */ public request<T = unknown, R = ApiResponse<T>>(config: RequestOptions): Promise<R> { return this.instance.request(config) } /** * GET请求 * @param url 请求地址 * @param params 请求参数 * @param options 请求配置 * @returns Promise */ public get<T, R = Record<string, unknown>>( url: string, params?: R, options?: RequestOptions, ): Promise<T> { return this.instance.get(url, { params, ...options }) } /** * POST请求 * @param url 请求地址 * @param data 请求数据 * @param params 请求参数 * @param options 请求配置 * @returns Promise */ public post<T, R = Record<string, unknown>>( url: string, data?: R, params?: R, options?: RequestOptions, ): Promise<T> { return this.instance.post(url, data, { params, ...options }) } /** * PUT请求 * @param url 请求地址 * @param data 请求数据 * @param options 请求配置 * @returns Promise */ public put<T>(url: string, data?: Record<string, unknown>, options?: RequestOptions): Promise<T> { return this.instance.put(url, data, options) } } // 导出请求实例 export const request = new Request(defaultConfig) // 导出默认实例 export default request -
定义api接口
如果后台服务还没搭建好,但是有相关接口规范文档,可以先定义接口,使用msw的mock方案
import request from '@/utils/request' export function testApi(){ return request.get('/api/test') } -
MSW配置
-
基础配置
// .env.development VITE_ENABLE_MOCK=true在开发环境中,可以通过修改 VITE_ENABLE_MOCK 变量来启用或禁用API模拟服务。当该值为 true 时,应用将使用MSW提供的模拟数据;当该值为 false 时,应用将使用真实的API服务。
-
Mock 规则编写
import { http, HttpResponse } from 'msw'; // Mock处理程序 const handlers = [ // 获取用户信息 http.get('/api/test', () => { return HttpResponse.json( createResponse<Record<string,string|number>>({ id: 1, username: 'admin', email: 'admin@example.com', role: 'admin', }), ) }), ]-
msw启动
// 创建MSW worker const worker = setupWorker(...handlers) /** * 启动MSW */ export function setupMSW() { // 检查是否启用mock服务 const enableMock = import.meta.env.VITE_ENABLE_MOCK === 'true' if (enableMock) { worker .start({ onUnhandledRequest: 'bypass', // 对未处理的请求直接放行 }) .catch(console.error) console.log('[MSW] Mock Service Worker 已启动') } } export default setupMSW //main.ts // 导入并启动MSW import { setupMSW } from './mocks' setupMSW()
-
至此msw继承完毕,在没有后端服务的情况下,可以使用这种方案,等后端服务搭建好后,可以你不用改动任何代码