从零开始搭建前端项目六(axios+mock)
从零开始一步一步搭建一个精简的前端项目。
技术栈:Vue3.0 + Vite + TypeScript + Element Plus + Vue Router + axios + Pinia
规范化:Eslint + Airbnb JavaScript Style + husky + lint-staged
包管理:yarn
历史内容
从零开始搭建前端项目一(Vue3+Vite+TS+Eslint+Airbnb+prettier)
从零开始搭建前端项目二(husky+lint-staged)
本章内容
在项目中加入mock,封装axios,搭建前端网络请求调试环境。
mock
安装
安装依赖。
yarn add mockjs vite-plugin-mock --dev
复制代码
配置
配置 vitePlugins.ts
(上一章已经把vite.config.ts
中的plugins
抽取到vitePlugins.ts
中)。
// vitePlugins.ts
import { viteMockServe } from 'vite-plugin-mock'; // ++
export default (env: ConfigEnv) => {
const isBuild = env.command === 'build';
const vitePlugins: (Plugin | Plugin[])[] = [
// ...
];
// ++
if (!isBuild) {
vitePlugins.push(
viteMockServe({
ignore: /^\_/,
mockPath: 'mock',
localEnabled: !isBuild,
// 实际开发关闭
prodEnabled: false,
injectCode: `
import { setupProdMockServer } from '../mock/_createProductionServer';
setupProdMockServer();
`,
})
);
}
return vitePlugins;
};
复制代码
在项目根目录下创建mock文件夹,在mock文件夹下创建_createProdMockServer.ts
文件,批量加载mock文件夹下的所有接口。
// _createProdMockServer.ts
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
// 批量加载
const modules = import.meta.globEager('./mock/*.ts');
const mockModules: Array<string> = [];
Object.keys(modules).forEach((key) => {
if (key.includes('/_')) {
return;
}
mockModules.push(...modules[key].default);
});
export default function setupProdMockServer() {
createProdMockServer(mockModules);
}
复制代码
测试
在mock文件夹下创建user.ts
,加入模拟登录接口。
import { MockMethod } from 'vite-plugin-mock';
export default [
{
url: '/textMock',
timeout: 200,
method: 'POST',
response: () => {
return {
code: 0,
data: {
token: 'eca4bb1529bb5b4dcd3c9aa68e9e185d',
expire: 1200,
onLine: true,
},
msg: 'success',
};
},
},
] as MockMethod[];
复制代码
运行,访问该api。
axios封装
安装
yarn add axios
复制代码
封装
在src
文件夹下创建utils->http文件夹,在http文件夹下创建baseAxios.ts
文件。
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import qs from 'qs';
import isPlainObject from 'lodash/isPlainObject';
import router from '@router/index';
import { IResponse, RequestOptions } from '@models/axios/axios';
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
axios.defaults.timeout = 1000 * 30;
// 表示跨域请求时是否需要使用凭证
axios.defaults.withCredentials = false;
// 允许跨域
axios.defaults.headers.post['Access-Control-Allow-Origin-Type'] = '*';
// 返回支持的状态码
axios.defaults.validateStatus = function (status: number) {
return status >= 200 && status <= 500;
};
const axiosInstance: AxiosInstance = axios.create({
baseURL: `${import.meta.env.BASE_URL}`,
});
// axios实例拦截响应
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
return response;
},
(error) => {
return Promise.reject(error);
}
);
// axios实例拦截请求
axiosInstance.interceptors.request.use(
(config: AxiosRequestConfig) => {
config.headers['Accept-Language'] = 'zh-CN';
// 防止get请求缓存
if (config.method === 'get') {
config.params = {
...config.params,
...{ _t: new Date().getTime() },
};
}
if (isPlainObject(config.data)) {
// eslint-disable-next-line no-param-reassign
config.data = {
...config.data,
};
if (config.headers?.['content-type']) {
const contentType: string = config.headers['content-type'] + '';
if (/^application\/x-www-form-urlencoded/.test(contentType)) {
// form形式编码
config.data = qs.stringify(config.data);
}
}
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const request = <T = any>(
config: AxiosRequestConfig,
options?: RequestOptions
): Promise<T> => {
const conf = config;
return new Promise((resolve, reject) => {
axiosInstance
.request<any, AxiosResponse<IResponse>>(conf)
.then((res: AxiosResponse<IResponse>) => {
const data: any = res;
resolve(data as T);
})
.catch((error) => reject(error));
});
};
// 封装get
export function get<T = any>(
config: AxiosRequestConfig,
options?: RequestOptions
): Promise<T> {
return request({ ...config, method: 'GET' }, options);
}
// 封装post
export function post<T = any>(
config: AxiosRequestConfig,
options?: RequestOptions
): Promise<T> {
return request({ ...config, method: 'POST' }, options);
}
export default request;
export type { AxiosInstance, AxiosResponse };
复制代码
接口
在src
文件下创建api
文件夹,在api
文件夹下创建各个接口定义,如创建userLogin.ts
文件。
import { post } from '@utils/http/baseAxios';
import { IResponse } from '@models/axios/axios';
import { LoginData } from '@models/user/user';
/**
* @description: 用户登录
* @params {LoginData} params
* @return {Promise}
*/
export const login = (params: LoginData): Promise<IResponse> => {
return post({ url: 'user/login', params }).then((res) => res.data);
};
复制代码
网络请求使用实例
在使用 async…await 方法时,经常采用 try…catch 捕获异常,如果有多个异步操作,需要每一次书写 try…catch。这样代码的简洁性较差,为了使代码更加的优雅,我们通过使用 await-to-js
js 库来处理异常。
安装await-to-js
yarn add await-to-js
复制代码
使用
<template>
<el-button @click="sendLogin">sendLogin</el-button>
</template><script setup lang="ts" name="Login">
import to from 'await-to-js';
import { login as userLogin} from '@api/userLogin';
import { IResponse } from '@models/axios/axios';
import { LoginData } from '@models/user/user';
const $router = useRouter();
const sendLogin = async () => {
const params = {
username: 'admin',
password: '123456',
} as LoginData;
const [err, res] = await to<IResponse>(userLogin(params));
if (err) {
// 错误处理
console.log(err);
}
const { code, data, msg } = res;
console.log(code, data, msg);
};
</script>
复制代码
小结
本篇介绍了在开发过程中如何使用mock模拟网络请求,封装axios,使用await-to-js更优雅地处理网络请求。