axios 在项目中的正确打开方式
在前端项目中,我们一般使用 axios 来与后端进行数据交互,axios 是一个基于 promise 的 http 库。如果你还没使用过 axios,可以先看一下文档。
项目结构
我们先来设计一下项目结构,为了方便管理和复用,我们将所有请求函数都放在一个文件夹下,并按功能划分成不同的 js 文件。
再封装一下 axios,使它更适用于我们的项目。
├── src
│ ├── api
│ │ ├── user.js 分功能划分请求函数文件
│ │ ├── search.js
│ │ └── work.js
│ │
│ └── units
└── http.js 封装 axios
如果你有需要引入多个功能文件的需求,那也可以在 api 文件夹下,新增一个 index.js,用来转发请求函数。举个例子,我们需要从 user 拿 getUser,从 work 拿 getWork 方法
import { getUser } from "@/api/user"
import { getWork } from "@/api/work"
我们可以新增一个 index.js
// index.js
export * from "./user.js"
export * from "./work.js"
这样,在使用时,就可以直接
import { getUser, getWork } from "@/api"
请求方法
请求方法我们写在 api 文件夹下对应功能的文件中,比如我们需要一个搜索的方法,那我们在 search.js 中
import http from "../units/http"
export function search({ wd }) {
return http.get("http://baidu.com/s", {
params: {
wd,
},
})
}
封装 axios
接下来,就开始封装 axios 了,让所有的请求都能共享这部分配置。直接先看 http.js 的代码吧
import axios from "axios"
import qs from "qs"
const http = axios.create({
timeout: 5000, // 超时时间
paramsSerializer: function(params) {
return qs.stringify(params, { arrayFormat: "repeat" })
},
})
export default http
这样的话,每个 http 请求都会在 5s 之后退出,且发送请求之前都会使用 qs 对参数做序列化。
baseURL
如果你的项目中,所有的请求都只在一个接口域名下,只有测试和生产环境的区别的话,那么可以配置一下 baseURL,这种情况可以通过 node 的环境变量来匹配 BaseURL
// http.js
http.defaults.baseURL = process.env.NODE_ENV === "development" ? "测试URL" : "生产URL"
实际请求的 URL 就为 baseURL + request url,比如这边配置的 baseURL 为 http://baidu.com/s
,那么 search 方法就可以将 url 写成 /s
export function search({ wd }) {
return http.get("/s", {
params: {
wd,
},
})
}
如果项目中需要去多个接口下请求,那不需要配置 baseURL,可以额外提供一个文件将 URL 暴露出来,比如
let USER_URL = "aaa"
let WALLET_URL = "bbb"
if (process.env.NODE_ENV === "development") {
USER_URL = "testa"
WALLET_URL = "testb"
}
export { USER_URL, WALLET_URL }
在请求的模块中,比如 user.js 中
import http from "../units/http"
import { USER_URL } from "xxx"
export function userInfo({ userId }) {
return http.get("${USER_URL}/s", {
params: {
userId,
},
})
}
拦截器
我们可以在请求发起,响应的时候进行额外的处理,在请求发起之前,我们如果需要做登录权限的控制,那就需要在请求头添加 token
http.interceptors.request.use(
(config) => {
let token = localStorage.getItem("token")
config.headers["Authorization"] = token
},
(error) => {
return Promise.reject(error)
}
)
在服务器对请求进行了响应之后,我们也可以用拦截器对结果进行处理,同时对一些错误码进行处理
http.interceptors.response.use(
(response) => response.data,
(error) => {
switch (error.response.status) {
case 401:
// 未登录的逻辑,如跳转到登录页
break
// 其他通用错误码的处理逻辑
}
return Promise.reject(error)
}
)
配合组件库,我们还可以在请求发起前,设置 loading,在请求响应后,结束 loading,贴上完整的 http.js
import axios from "axios"
import qs from "qs"
import { Loading, Message } from "element-ui"
const http = axios.create({
timeout: 5000,
})
http.defaults.paramsSerializer = (params) => {
return qs.stringify(params, { arrayFormat: "repeat" })
}
let loading
http.interceptors.request.use(
(config) => {
let token = localStorage.getItem("token")
config.headers["Authorization"] = token
if (!config?.noLoading) {
if (loading) {
loading?.close()
}
loading = Loading.service({
lock: true,
target: config?.el || document.body,
text: "数据正在加载中",
spinner: "el-icon-loading",
background: "transparent",
customClass: "loading",
})
}
return config
},
(error) => {
loading?.close()
return Promise.reject(error)
}
)
http.interceptors.response.use(
(response) => {
loading?.close()
return response.data
},
(error) => {
loading?.close()
switch (error?.response?.status) {
case 401:
Message.error("请先登录")
localStorage.removeItem("token")
location.href = "/"
break
case 412:
Message.error("密码输入错误")
break
case 403:
Message.error("权限不足,请调整后重试")
}
return Promise.reject(error)
}
)
export default http
使用
让我们重新看下请求函数
// user.js
import { USER_URL } from "@/api"
import http from "../units/http"
export function getUserList({ el }) {
return http.get(`${USER_URL}/userlist`, {
el, // 额外配置的 el,标示loading挂载的节点
})
}
export function getUserInfo({ userId }) {
return http.get(`${USER_URL}/userinfo`, {
params: {
userId,
},
noLoading: true, // 额外拓展的 config,标示不需要 loading
})
}
在组件中调用时,就可以
getUserlist({ el: "#list" }) // loading 会挂载在 #list下
.then((res) => {})
.catch((e) => {})
getUserInfo({ userId }) // 没有 loading
.then((res) => {})
.catch((e) => {})
config 优先级
新建 axios 实例的 config 优先级最低,如
const http = axios.craete({})
// 此时 timeout 默认为 0
http.defaults.timeout = 2000
// timeout 被改写成了 2s
在实际发起请求时,又可以重新将这个配置覆盖
export function getUserInfo({ userId }) {
return http.get(`${USER_URL}/userinfo`, {
params: {
userId,
},
timeout: 5000,
noLoading: true,
})
}
那么,此时 getUserInfo 这个函数就需要 5s,才会超时