小程序或 uni-app 提供了专门用于网络请求的 API ,但结合实际开发还需要扩展一些与业务相关的逻辑,如基地址、拦截器等功能,通常会对 uni.request
进行封装,luch-request
就是这样一个工具模块,它仿照 axios 的用法对 uni.request
进行二次封装,扩展了基地址、拦截器等业务相关的功能。
- 安装
luch-request
npm install luch-request
2.实例化并配置基地址,项目根目录新建 utils/http.js
// utils/http.js
// 导入模块
import Request from 'luch-request'
// 实例化网络请求
const http = new Request({
// 接口基地址
baseURL: 'https://t1ps66c7na.hk.aircode.run',
})
// 导出配置好的模网络模块
export { http }
普通用法
<!-- pages/test/index.vue -->
<script setup>
import { http } from '@/utils/http.js'
function onButtonClick() {
// 1. 普通用法
http.request({
url: '/echo',
method: 'GET',
header: {
customHeader: '22222222'
}
})
}
</script>
<template>
<view class="content">
<button @click="onButtonClick" type="primary">luch-request 测试</button>
</view>
</template>
3.配置请求拦截器
在请求之前执行一些逻辑,例如检测登录状态,添加自定义头信息等。
// utils/http.js
// 导入模块
import Request from 'luch-request'
// 实例化网络请求
const http = new Request({
// 接口基地址
baseURL: 'https://t1ps66c7na.hk.aircode.run',
})
// 请求拦截器
http.interceptors.request.use(
function (config) {
// 定义头信息,并保证接口调用传递的头信息
// 能够覆盖在拦截器定义的头信息
config.header = {
Authorization: '11111111',
...config.header,
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 导出配置好的模网络模块
export { http }
以上代码中要注意拦截器中配置的头信息不要将原有的头信息覆盖。
4.配置响应拦截器
// utils/http.js
// 导入模块
import Request from 'luch-request'
// 实例化网络请求
const http = new Request({
// 接口基地址
baseURL: 'https://t1ps66c7na.hk.aircode.run',
})
// 请求拦截器
http.interceptors.request.use(
function (config) {
// 定义头信息,并保证接口调用传递的头信息
// 能够覆盖在拦截器定义的头信息
config.header = {
Authorization: '11111111',
...config.header,
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 响应拦截器
http.interceptors.response.use(
function ({ statusCode, data, config }) {
// 解构出响应主体
return data
},
function (error) {
return Promise.reject(error)
}
)
// 导出配置好的模网络模块
export { http }
使用方法
<!-- pages/test/index.vue -->
<script setup>
import { http } from '@/utils/http.js'
async function onButtonClick() {
// 1. 普通用法
const result = await http.request({
url: '/echo',
method: 'GET',
header: {
customHeader: '22222222'
}
})
console.log(result)
}
</script>
<template>
<view class="content">
<button @click="onButtonClick" type="primary">luch-request 测试</button>
</view>
</template>
5.请求加载状态
在发请求之前展示一个加载提示框,请求结束后隐藏这个提示框,该部分的逻辑分别对应请求拦截器和响应拦截器,在请求拦截器中调用 uni.showLoading
在响应拦截器中调用 uni.hideLoading
。
在设置加载提示框之前先来了解一下 luch-request
提供的自定义配置参数的功能,即 custom
属性,该属性的用法如下:
// utils/http.js
// 导入模块
import Request from 'luch-request'
// 实例化网络请求
const http = new Request({
// 接口基地址
baseURL: 'https://t1ps66c7na.hk.aircode.run',
custom: {
abc: 123,
loading: true
}
})
// 省略以下部分代码...
局部配置了相同的自定义参数时会覆盖全局配置的自定义参数
<!-- pages/test/index -->
<script setup>
import { http } from '@/utils/http.js'
async function onButtonClick() {
// 1. 普通用法
const result = await http.request({
// 省略部分代码...
// 局部配置自定义参数
custom: {
abc: 123,
},
// 省略部分代码...
})
console.log(result)
}
</script>
在了解自定义参数的使用后,我们来自定义一个能控制是否需要 loading 提示框的属性,全局默认为 true
。
// utils/http.js
// 导入模块
import Request from 'luch-request'
// 实例化网络请求
const http = new Request({
// 接口基地址
baseURL: 'https://t1ps66c7na.hk.aircode.run',
custom: {
loading: true
}
})
// 请求拦截器
http.interceptors.request.use(
function (config) {
// 显示加载状态提示
if (config.custom.loading) {
uni.showLoading({ title: '正在加载...', mask: true })
}
// 定义头信息,并保证接口调用传递的头信息
// 能够覆盖在拦截器定义的头信息
config.header = {
Authorization: '11111111',
...config.header,
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 响应拦截器
http.interceptors.response.use(
function ({ statusCode, data, config }) {
// 隐藏加载状态提示
uni.hideLoading()
// 解构出响应主体
return data
},
function (error) {
return Promise.reject(error)
}
)
// 导出配置好的模网络模块
export { http }
到此关于网络请求的基本用法就封装完毕了,后续会补充登录权限检测的业务逻辑。
http文件
import request from 'luch-request'
import { useUserStore } from '../stores/user'
// tabBar页面路径,方便筛选
const tabBarList = [
'pages/index/index',
'pages/wiki/index',
'pages/notify/index',
'pages/my/index',
]
// 实例化网络请求模块
const http = new request({
// 接口基地址
baseURL: 'https://consult-api.itheima.net',
// 设置loading默认值
custom: {
loading: true,
},
})
// 请求响应器
http.interceptors.request.use(
(config) => {
// 添加加载事件,当loading等于true执行
if (config.custom.loading) {
uni.showLoading({
title: '正在加载',
})
}
// 用户相关的数据
const userStore = useUserStore()
// 全局默认的头信息(方便以后扩展)
const defaultHeader = {}
// 判断是否存在 token 并且不在接口白单当中
if (userStore.token) {
defaultHeader.Authorization = 'Bearer ' + userStore.token
}
// 合并全局头信息和局部头信息(局部优先级高全局)
config.header = {
...defaultHeader,
...config.header,
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 响应拦截器
http.interceptors.response.use(
(res) => {
// 加载完成
uni.hideLoading()
return res.data
},
function (error) {
// 请求失败
uni.hideLoading()
// 后端约定 token 过期(失效)时,状态码值为 401
if (error.statusCode === 401) reLogin()
return Promise.reject(error)
}
)
// 引导用户重新登录
function reLogin() {
// 动态读取当前页面的路径
const pageStack = getCurrentPages()
const currentPage = pageStack[pageStack.length - 1]
// 完整的路由(包含地址中的参数)
const redirectURL = currentPage.$page.fullPath
// 是否为 tabBar 中定义的路径
const openType = tabBarList.includes(currentPage.route)
? 'switchTab'
: 'redirectTo'
// 用户相关数据
const userStore = useUserStore()
// 将来再跳转回这个页面
userStore.redirectURL = redirectURL
// 页面(路由)跳转方式
userStore.openType = openType
// 跳转到登录页面
uni.redirectTo({
url: `/pages/login/index`,
})
// 清空pinia以及本地数据
userStore.token = ''
}
export { http }