什么是 axios? Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。开箱即用的工具
这也是 Vue 官方推荐使用的发送 ajax 请求的工具
axios有哪些特性 安装 axios 1 2 3 npm install axios --save yarn add axios --save
在 src/App.vue
中使用一下 axios
,添加以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <el-config-provider :locale ="zhCn" > <div > <h1 > 请求的内容</h1 > <div > {{ result }}</div > </div > </el-config-provider > </template > <script lang ="ts" setup > import axios from "axios" ;const result = ref("" );axios.get(location.href).then(({ data } ) => { result.value = data; }); </script >
在浏览器中就可以查看到我们网站的代码了
在日常项目的开发中,通常会有多个后端的服务,而每个服务会有自己的 url 地址、token之类的,通常是使用单独的 axios
的实例处理对应的服务,这就需要我们封装多个 axios
的实例
封装 axios 在 src
目录下新建一个 axios
文件夹,在其中新建一个 index.ts
文件,这个文件就是用来导出封装好的 axios
使用 axios.create()
方法就可以创建一个 axios
的实例
axios
可以设置全局默认的配置,这些配置会被每个实例共享,实例可以覆盖这些配置
设置全局的参数 设置全局的接口超时时间 timeout
,这个时间是对所有实例通用的 src/axios/index.ts
1 2 import axios from "axios" ;axios.defaults.timeout = 60 * 1000 ;
创建 axios的实例 假设我们的项目中会用到两个服务(a, b)的接口
同一个服务根据不同的运行环境需要使用不同的接口地址,这种情况最好是把接口地址写在环境文件中
.env.development 中添加
1 2 VUE_APP_A_BASE_URL=http://a.dev.server.com VUE_APP_B_BASE_URL=http://b.dev.server.com
.env.t 中添加
1 2 VUE_APP_A_BASE_URL=http://a.test.server.com VUE_APP_B_BASE_URL=http://b.test.server.com
.env.production 中添加
1 2 VUE_APP_A_BASE_URL=http://a.prod.server.com VUE_APP_B_BASE_URL=http://b.prod.server.com
在 src/axios/index.ts
文件中就可以通过 process.env.VUE_APP_A_BASE_URL
和 process.env.VUE_APP_B_BASE_URL
取到对应的服务地址
src/axios/index.ts 添加
1 2 3 4 5 6 7 export const aService = axios.create({ baseURL : process.env.VUE_APP_A_BASE_URL, }); export const bService = axios.create({ baseURL : process.env.VUE_APP_B_BASE_URL, });
创建并导出的两个 axios 的服务分别用来处理对应的两个后端服务的接口
设置请求拦截器 请求拦截器可以处理哪些问题 在请求发送之前需要处理的事情,都可以在请求拦截器中处理 例如每个请求都要在请求头中添加 token
在请求发送之前遇到的错误都可以在请求拦截器中被处理 语法 axios
设置请求拦截器的语法,要为实例添加拦截器只要把 axios
替换成对应的实例就可以了
1 axios.interceptors.request.use(onFulfilled, onRejected);
onFulfilled
函数,接收一个 config 的参数,可以对 config 进行修改,最后返回这个 configonRejected
函数,处理在请求发送之前的错误请求拦截器的执行顺序是先添加的后执行
封装 在 src/axios
下新建文件夹 interceptors
再 interceptors
新建一个 request
文件夹,这个文件夹中保存所有请求拦截器
请求错误处理的拦截器 把错误拦截器作为第一个拦截器,可以拦截到所有请求发出之前的错误,在这个也可以给服务器上报错误日志
同一个拦截器中的 onRejected
函数处理不到 onFulfilled
中的异常,所以最后一个请求拦截器的 onRejected
函数是不会被执行的
在 src/axios/interceptors/request
文件夹下新建 error.ts
文件,这个文件中做对请求开始之前的错误处理
src/axios/interceptors/request/error.ts
1 2 3 4 export default function handleRequestError (error: any ): Promise <any > { return Promise .reject(error); }
eslint 提示 Unexpected any. Specify a different type. 在项目根目录下的 .eslintrc.js
文件中 rules
中添加一行 "@typescript-eslint/no-explicit-any": "off",
eslint 提示 Argument ‘error’ should be typed with a non-any type. 在项目根目录下的 .eslintrc.js
文件中 rules
中添加一行 "@typescript-eslint/explicit-module-boundary-types": "off",
在 src/axios/interceptors/request
文件夹下新建 index.ts
文件,这个文件提供两个添加请求拦截器的方法
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 import { AxiosInstance, AxiosRequestConfig } from "axios" ;import handleRequestError from "./error" ;export function useBaseRequestInterceptor ( axiosService: AxiosInstance, requestInterceptorArray: [ (config: AxiosRequestConfig) => AxiosRequestConfig, ((error: any ) => any ) | undefined ][] ): AxiosInstance { requestInterceptorArray.forEach((interceptor ) => axiosService.interceptors.request.use(...interceptor) ); return axiosService; } export default function useRequestInterceptor ( axiosService: AxiosInstance, requestInterceptorArray: Array < (config: AxiosRequestConfig) => AxiosRequestConfig > = [] ) { const interceptors: [ (config: AxiosRequestConfig ) => AxiosRequestConfig, undefined ][] = requestInterceptorArray.map((interceptor ) => [interceptor, undefined ]); return useBaseRequestInterceptor(axiosService, [ [(config: AxiosRequestConfig ) => config, handleRequestError], ...interceptors, ]); }
给 aService
和 bService
添加拦截器
src/axios/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 import useRequestInterceptor from "./interceptors/request" ;export const aService = useRequestInterceptor( axios.create({ baseURL : process.env.VUE_APP_A_BASE_URL, }) ); export const bService = useRequestInterceptor( axios.create({ baseURL : process.env.VUE_APP_B_BASE_URL, }) );
在 headers
中添加 token
的请求拦截器 在 src/axios/interceptors/request
文件夹下新建 token.ts
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import { AxiosRequestConfig } from "axios" ;export function handleAServiceRequestToken (config: AxiosRequestConfig ) { const token = "a-service-token" ; config.headers["token" ] = token; return config; } export function handleBServiceRequestToken (config: AxiosRequestConfig ) { const token = "b-service-token" ; config.headers["token" ] = token; return config; }
给 aService
和 bService
添加 token
拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { handleAServiceRequestToken, handleBServiceRequestToken, } from "./interceptors/request/token" ; export const aService = useRequestInterceptor( axios.create({ baseURL : process.env.VUE_APP_A_BASE_URL, }), [handleAServiceRequestToken] ); export const bService = useRequestInterceptor( axios.create({ baseURL : process.env.VUE_APP_B_BASE_URL, }), [handleBServiceRequestToken] );
还有其他需要在请求拦截器做的,都可以按照这种方式添加
设置响应拦截器 响应拦截器可以处理哪些问题 响应拦截器会在请求成功或失败之后调用
请求的错误 在这里判断服务器返回的数据是否有效,无效数据可以当作异常处理 如果接口返回的是文件可以在这里完成文件的下载 等等。。。 语法 axios
设置响应拦截器的语法,要为实例添加拦截器只要把 axios
替换成对应的实例就可以了
1 axios.interceptors.response.use(onFulfilled, onRejected);
onFulfilled
函数,接收一个 AxiosResponse
的返回结果,可以对处理返回的接口进行修改,最后返回一个结果onRejected
函数,处理请求异常的错误响应拦截器的执行顺序是先添加的先执行
建议在拦截器中返回 result
,方便后续插拔拦截器的处理,可以考虑在错误拦截器之前的最后一个拦截器中返回 result.data
封装 在 src/axios/interceptors
下新建一个 response
文件夹,这个文件夹中保存所有响应拦截器
同一个拦截器中的 onRejected
函数处理不到 onFulfilled
中的异常,所以第一个响应拦截器的 onRejected
函数是不会被执行的
响应错误处理的拦截器 在 src/axios/interceptors/response
文件夹下新建 error.ts
文件,这个文件中做对请求响应之后的错误拦截
把错误拦截器作为最后一个拦截器,可以拦截到所有响应拦截器中的错误
src/axios/interceptors/response/error.ts
1 2 3 4 5 6 7 8 9 10 11 12 import { ElMessage } from "element-plus" ;export default function handleResponseError (error: any ): Promise <any > { ElMessage.error(error); return Promise .reject(error); }
在 src/axios/interceptors/response
文件夹下新建 index.ts
文件,这个文件提供两个添加响应拦截器的方法
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 import { AxiosInstance, AxiosResponse } from "axios" ;import handleResponseError from "./error" ;export function useBaseResponseInterceptor ( axiosService: AxiosInstance, responseInterceptorArray: [ (result: AxiosResponse) => any , ((error: any ) => any ) | undefined ][] ): AxiosInstance { responseInterceptorArray.forEach((interceptor ) => axiosService.interceptors.response.use(...interceptor) ); return axiosService; } export default function useResponseInterceptor ( axiosService: AxiosInstance, responseInterceptorArray: Array <(result: AxiosResponse) => any > = [] ) { const interceptors: [(result: AxiosResponse ) => any , undefined ][] = responseInterceptorArray.map((interceptor ) => [interceptor, undefined ]); return useBaseResponseInterceptor(axiosService, [ ...interceptors, [(res: AxiosResponse ) => res, handleResponseError], ]); }
给 aService
和 bService
添加响应拦截器
src/axios/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import useResponseInterceptor from "./interceptors/response" ;export const aService = useRequestInterceptor( useResponseInterceptor( axios.create({ baseURL : process.env.VUE_APP_A_BASE_URL, }), [] ), [handleAServiceRequestToken] ); export const bService = useRequestInterceptor( useResponseInterceptor( axios.create({ baseURL : process.env.VUE_APP_B_BASE_URL, }), [] ), [handleBServiceRequestToken] );
处理后端返回的数据,假设后端返回的 json 中有 code 字段,且只有 200 表示成功,其他的全部按照异常处理,如果异常的话会有 message 字段返回异常信息,在 src/axios/interceptors/response
下新建 filterResponse.ts
文件 src/axios/interceptors/response/filterResponse.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { AxiosResponse } from "axios" ;import { ElMessage } from "element-plus" ;export default function handleResponseFilter ( response: AxiosResponse ): AxiosResponse | Promise <AxiosResponse > { const { data } = response; if (data.code !== 200 ) { ElMessage.error(data.message); return Promise .reject(response); } return response; }
src/axios/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import handleResponseFilter from "./interceptors/response/filterResponse" ;export const aService = useRequestInterceptor( useResponseInterceptor( axios.create({ baseURL : process.env.VUE_APP_A_BASE_URL, }), [handleResponseFilter] ), [handleAServiceRequestToken] ); export const bService = useRequestInterceptor( useResponseInterceptor( axios.create({ baseURL : process.env.VUE_APP_B_BASE_URL, }), [handleResponseFilter] ), [handleBServiceRequestToken] );
其他响应拦截器也可以使用这种方式添加
接口请求中展示loading 根据具体项目看要不要添加这个拦截器,这里用来做演示
在请求发出后 500ms 内如果收到了响应就不展示 loading,如果超过 500ms 就展示 loading,在接口响应之后关闭 loading。
在 src/axios
下创建一个 utils
文件夹,在其中新建一个 loading.ts
文件,用来显示和关闭 loading
src/axios/utils/loading.ts
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 import { ElLoading, ILoadingInstance } from "element-plus" ;let loadingCount = 0 ;let loading: ILoadingInstance | null = null ;export function showLoading ( ): NodeJS .Timeout { loadingCount++; return setTimeout (() => { if (!loading && loadingCount) { loading = ElLoading.service(); } }, 500 ); } export function hideLoading ( ) { loadingCount--; if (loadingCount === 0 && loading) { loading.close(); loading = null ; } }
在 src/axios/interceptors/request
和 src/axios/interceptors/response
文件夹下新建 loading.ts
文件,在 src/axios/utils
下新建 constants.ts
文件,用来存放常量
src/axios/utils/constants.ts
1 export const HEADER_NO_LOADING = "NO-LOADING" ;
src/axios/interceptors/request/loading.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { AxiosRequestConfig } from "axios" ;import { showLoading } from "../../utils/loading" ;import { HEADER_NO_LOADING } from "../../utils/constants" ;export default function handleRequestLoading ( config: AxiosRequestConfig ): AxiosRequestConfig { const { headers } = config; if (!headers[HEADER_NO_LOADING]) { showLoading(); } return config; }
src/axios/interceptors/response/loading.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { AxiosResponse } from "axios" ;import { hideLoading } from "../../utils/loading" ;import { HEADER_NO_LOADING } from "../../utils/constants" ;export default function handleResponseLoading ( result: AxiosResponse ): AxiosResponse { const { config : { headers }, } = result; if (!headers[HEADER_NO_LOADING]) { hideLoading(); } return result; }
如果带有loading的请求出现了异常,要在 error
拦截器中关闭 loading
src/axios/interceptors/response/error.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { AxiosError } from "axios" ;import { ElMessage } from "element-plus" ;import { hideLoading } from "../../utils/loading" ;import { HEADER_NO_LOADING } from "@/axios/utils/constants" ;export default function handleResponseError ( error: AxiosError ): Promise <AxiosError > { if (!error.config.headers[HEADER_NO_LOADING]) { hideLoading(); } ElMessage.error(error.message); return Promise .reject(error); }
src/axios/index.ts
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 import handleRequestLoading from "./interceptors/request/loading" ;import handleResponseLoading from "./interceptors/response/loading" ;export const aService = useRequestInterceptor( useResponseInterceptor( axios.create({ baseURL : process.env.VUE_APP_A_BASE_URL, }), [handleResponseLoading, handleResponseFilter] ), [handleAServiceRequestToken, handleRequestLoading] ); export const bService = useRequestInterceptor( useResponseInterceptor( axios.create({ baseURL : process.env.VUE_APP_B_BASE_URL, }), [handleResponseLoading, handleResponseFilter] ), [handleBServiceRequestToken, handleRequestLoading] );
loading的拦截器就加完了,其他的项目中需要的拦截器也可以按照这种方式加入
配置反向代理 如果项目开发中,后端的接口提供了跨域请求的支持,那这一步就可以不做了。
但是通常在项目开发中,后端的接口不会提供跨域请求的支持,这个时候就需要我们对请求做一个反向代理,webpack
通过 devServer.proxy
内置了这样的服务。
在根目录的 vue.config.js
文件中添加配置即可
例:给 a、b 服务配置代理 / 代理到 http://localhost:8082
(本地的服务启动的是多少就代理到多少)
首先把 a、b 两个服务的 axios
实例创建时的 baseURL
改成唯一的前缀
src/axios.index.ts
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 export const aService = useRequestInterceptor( useResponseInterceptor( axios.create({ baseURL : "/aServer" , }), [handleResponseLoading, handleResponseFilter] ), [handleAServiceRequestToken, handleRequestLoading] ); export const bService = useRequestInterceptor( useResponseInterceptor( axios.create({ baseURL : "/bServer" , }), [handleResponseLoading, handleResponseFilter] ), [handleBServiceRequestToken, handleRequestLoading] );
然后在 vue.config.js
中配置对应的代理
vue.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 module .exports = { devServer : { proxy : { "/aServer" : { target : process.env.VUE_APP_A_BASE_URL, changeOrigin : true , secure : false , pathRewrite : { "/aServer" : "" , }, }, "/bServer" : { target : process.env.VUE_APP_B_BASE_URL, changeOrigin : true , secure : false , pathRewrite : { "/bServer" : "" , }, }, }, }, }
这样在启动开发环境的时候,就对我们的请求做了反向代理,避免了跨域问题的产生
在项目上线的时候,一般会用一个静态资源服务器做前端入口,比如 nginx,那就可以在 nginx 上做反向代理
上线时候的反向代理 用 nginx
做反向代理,一般会在项目根目录有一个 deployment
的文件夹,会存放一项目上线用到的配置文件,比如 nginx 的配置文件
在项目根目录下新建 deployment
文件夹,并在其中新建 test
和 prod
两个文件夹,在建立一个 nginx.conf.tmp
文件,这个文件作为我们生成 nginx.conf
的模版文件,需要根据自己项目的部署情况修改参数。
deployment/nginx.conf.tmp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server { listen 80 ; root /usr/share/nginx/html/dist; location / { try_files $uri $uri/ /index.html; } location ~ .*\.(js|css)$ { expires 30d ; } location ~ /.ht { deny all; } }
在新建一个 genNginxConf.js
这个文件可以根据第一个参数创建对应环境的 default.conf
文件,这是部署项目用到的 nginx
的配置文件
deployment/genNginxConf.js
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 const fs = require ("fs" );const path = require ("path" );const dotenv = require ("dotenv" );const dotenvExpand = require ("dotenv-expand" );const configFile = path.join(__dirname, "../vue.config.js" );const nginxFileName = "default.conf" ;const configBaseFile = path.join(__dirname, "./nginx.conf.tmp" );const mode = process.argv[2 ];let filePath;let envPath;if (mode === "t" || mode === "test" ) { filePath = path.join(__dirname, "test" , nginxFileName); envPath = path.join(__dirname, "../.env.t" ); } else if (mode === "prod" ) { filePath = path.join(__dirname, "prod" , nginxFileName); envPath = path.join(__dirname, "../.env.production" ); } else { console .log(`没有对应的模式:${mode} ` ); process.exit(1 ); } const env = dotenv.config({ path : envPath });dotenvExpand(env); let proxyPass = ` # 配置反向代理` ;if (fs.statSync(configFile).isFile()) { const webpackConfig = require (configFile); const proxy = webpackConfig?.devServer?.proxy; if (proxy) { for (const [k, { target, pathRewrite }] of Object .entries(proxy)) { let metaPath = k; if (pathRewrite) { Object .keys(pathRewrite).forEach((p ) => { metaPath = metaPath.replace(p, pathRewrite[p]); }); } proxyPass += ` location ${k} { proxy_pass ${target} ${metaPath} ; } ` ; } } else { console .log("没有配置 devServer.proxy,不需要设置代理" ); } } else { console .log("没有 vue.config.js 文件,不需要设置代理" ); } const tmpStr = fs.readFileSync(configBaseFile, { encoding : "utf8" });fs.writeFileSync( filePath, tmpStr.replace("# proxy_pass placeholder" , proxyPass) ); console .log("nginx 配置文件生成" );
命令行执行
1 node ./deployment/genNginxConf.js t
就可以看到 deployment/test/default.conf
文件被创建出来
把刚才的命令添加到 package.json
中的 scripts
中,方便之后的执行
1 2 "gennginx:t" : "node ./deployment/genNginxConf.js t" ,"gennginx:prod" : "node ./deployment/genNginxConf.js prod"
执行一下下面的命令,在 deployment/prod
下也会生层 default.conf
文件
1 2 3 npm run gennginx:prod yarn gennginx:prod
修改一下 build
和 build:t
命令,在执行这个命令的时候,可以同时生成新的 nginx
配置文件
1 2 "build" : "vue-cli-service build && npm run gennginx:prod" ,"build:t" : "vue-cli-service build --mode t && npm run gennginx:t" ,
完结 项目已经上传到 github 和 gitee
GitHub: https://github.com/wukang0718/cli-create-project
Gitee: https://gitee.com/wu_kang0718/cli-create-project
下一篇:页面基本框架