目录
b)application/x-www-form-urlencoded
一、前言
在前文中,我们介绍了uniapp中如何发送请求,并完成了基础的二次封装。虽然直接复用示例代码可以快速上手,但面对复杂项目时,往往需要根据实际需求重新设计网络请求模块。
对于前端经验较少的开发者来说,深度封装可能具有一定挑战性。本文旨在梳理不同项目的封装思路,帮助大家掌握核心逻辑,从而能够灵活应对各种业务场景的需求变化。
二、uniapp官方文档
首先给出uniapp发送网络请求的官网文档,然后再带领大家一起解读。uni.request(OBJECT) | uni-app官网
uni.request(OBJECT)
OBJECT 参数说明
参数名 | 类型 | 必填 | 默认值 | 说明 | 平台差异说明 |
---|---|---|---|---|---|
url | String | 是 | 开发者服务器接口地址 | ||
data | Object/String/ArrayBuffer | 否 | 请求的参数 | App 3.3.7 以下不支持 ArrayBuffer 类型 | |
header | Object | 否 | 设置请求的 header,header 中不能设置 Referer | App、H5端会自动带上cookie,且H5端不可手动修改 | |
method | String | 否 | GET | 有效值详见下方说明 | |
timeout | Number | 否 | 60000 | 超时时间,单位 ms | H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序 |
dataType | String | 否 | json | 如果设为 json,会对返回的数据进行一次 JSON.parse,非 json 不会进行 JSON.parse | |
responseType | String | 否 | text | 设置响应的数据类型。合法值:text、arraybuffer | 支付宝小程序不支持 |
sslVerify | Boolean | 否 | true | 验证 ssl 证书 | 仅App安卓端支持(HBuilderX 2.3.3+),不支持离线打包 |
withCredentials | Boolean | 否 | false | 跨域请求时是否携带凭证(cookies) | 仅H5支持(HBuilderX 2.6.15+) |
firstIpv4 | Boolean | 否 | false | DNS解析时优先使用ipv4 | 仅 App-Android 支持 (HBuilderX 2.8.0+) |
enableHttp2 | Boolean | 否 | false | 开启 http2 | 微信小程序 |
enableQuic | Boolean | 否 | false | 开启 quic | 微信小程序 |
enableCache | Boolean | 否 | false | 开启 cache | 微信小程序、抖音小程序 2.31.0+ |
enableHttpDNS | Boolean | 否 | false | 是否开启 HttpDNS 服务。如开启,需要同时填入 httpDNSServiceId 。 HttpDNS 用法详见 移动解析HttpDNS | 微信小程序 |
httpDNSServiceId | String | 否 | HttpDNS 服务商 Id。 HttpDNS 用法详见 移动解析HttpDNS | 微信小程序 | |
enableChunked | Boolean | 否 | false | 开启 transfer-encoding chunked | 微信小程序 |
forceCellularNetwork | Boolean | 否 | false | wifi下使用移动网络发送请求 | 微信小程序 |
enableCookie | Boolean | 否 | false | 开启后可在headers中编辑cookie | 支付宝小程序 10.2.33+ |
cloudCache | Object/Boolean | 否 | false | 是否开启云加速(详见云加速服务) | 百度小程序 3.310.11+ |
defer | Boolean | 否 | false | 控制当前请求是否延时至首屏内容渲染后发送 | 百度小程序 3.310.11+ |
success | Function | 否 | 收到开发者服务器成功返回的回调函数 | ||
fail | Function | 否 | 接口调用失败的回调函数 | ||
complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
method 有效值
注意:method有效值必须大写,每个平台支持的method有效值不同,详细见下表。
method | App | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 抖音小程序、飞书小程序 | 快手小程序 | 京东小程序 |
---|---|---|---|---|---|---|---|---|
GET | √ | √ | √ | √ | √ | √ | √ | √ |
POST | √ | √ | √ | √ | √ | √ | √ | √ |
PUT | √ | √ | √ | x | √ | √ | x | x |
DELETE | √ | √ | √ | x | √ | x | x | x |
CONNECT | x | √ | √ | x | x | x | x | x |
HEAD | √ | √ | √ | x | √ | x | x | x |
OPTIONS | √ | √ | √ | x | √ | x | x | x |
TRACE | x | √ | √ | x | x | x | x | x |
可见,请求方式主推使用GET、POST。
success 返回参数说明
参数 | 类型 | 说明 |
---|---|---|
data | Object/String/ArrayBuffer | 开发者服务器返回的数据 |
statusCode | Number | 开发者服务器返回的 HTTP 状态码 |
header | Object | 开发者服务器返回的 HTTP Response Header |
cookies | Array.<string> |
开发者服务器返回的 cookies,格式为字符串数组 |
三、举例演示
3.1 使用说明
这里我们找一个免费的api接口进行测试。
mounted() {
this.test()
},
methods: {
test() {
uni.request({
url: 'https://route.showapi.com/9-2?appKey=E53C00b070ce4Ee0b2B77365af35993c',
method: 'POST',
header: { 'content-type': 'application/x-www-form-urlencoded' },
data:{
area: '重庆'
},
success: (res) => {
console.log("请求成功",res);
}
})
}
}
打开页面调用控制台看一下
调用成功!
3.2 Content-Type
3.2.1 基本概念
Content-Type 是 HTTP 协议中用于标识请求或响应体数据类型的头部字段,属于 MIME(多用途互联网邮件扩展)类型的一种。其格式为:
Content-Type: <type>/<subtype>; <parameters>
type:主类型(如
text
、application
、image
)subtype:子类型(如
html
、json
、jpeg
)parameters:可选参数(如
charset=UTF-8
、boundary
用于分段数据)
3.2.2 核心作用
客户端请求:告知服务器如何解析请求体(如 JSON 或表单数据)
服务器响应:指导客户端(如浏览器)如何处理返回的数据(如渲染 HTML 或下载文件)
3.2.3 常见 Content-Type
类型及使用场景
1)文本类
a)text/plain
纯文本数据,适用于日志或简单消息。
Content-Type: text/plain; charset=UTF-8
b)text/html
HTML 文档,用于网页渲染。
// 后端设置示例
res.setHeader('Content-Type', 'text/html');
2)应用类
a)application/json
JSON格式数据,适合传输结构化数据(如 API 交互)。
前端请求示例:
fetch('/api', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' })
});
后端接收(Spring Boot)
@PostMapping("/api")
public ResponseEntity<?> handleJson(@RequestBody User user) { ... }
b)application/x-www-form-urlencoded
表单默认提交格式,数据编为
key=value&key2=value2
前端请求示例
uni.request({
url: 'https://route.showapi.com/9-2?appKey=E53C00b070ce4Ee0b2B77365af35993c',
method: 'POST',
header: { 'content-type': 'application/x-www-form-urlencoded' },
data:{
area: '重庆'
},
success: (res) => {
console.log("请求成功",res);
}
})
后端SpringBoot接收方式
- 使用
@RequestParam
注解- 适用于直接接收表单字段,字段名需与前端参数名一致
@PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password) { return "用户名:" + username + ",密码:" + password; }
- 注意:若参数名不一致,需指定
@RequestParam("前端字段名")
- 通过
Map
或LinkedHashMap
接收- 适用于动态或大量字段的场景:
@PostMapping("/submit") public ResponseEntity<?> submit(@RequestParam Map<String, String> params) { System.out.println(params); // 输出所有键值对 return ResponseEntity.ok(params); }
- 优点:无需预定义字段,灵活性强
- 避免使用
@RequestBody
x-www-form-urlencoded
数据默认不支持@RequestBody
绑定到自定义对象,否则会报错Content-Type not supported
。若需绑定到对象,需改用@ModelAttribute
或直接通过字段接收。
3)
多媒体类
image/jpeg
、video/mp4
分别用于 JPEG 图片和 MP4 视频,常见于资源加载或文件下载。
响应头示例
Content-Type: image/png
Content-Disposition: attachment; filename="example.png"
4)特殊类型
application/octet-stream
二进制流,适用于任意文件下载
// 后端设置
res.setHeader('Content-Type', 'application/octet-stream');
3.2.4 关键注意事项
字符编码
文本类数据需指定 charset
(如 UTF-8
),避免乱码。
与后端的匹配
- 使用
@RequestBody
接收 JSON 时,必须设置Content-Type: application/json
。 - 表单数据需对应
@RequestParam
,设置application/x-www-form-urlencoded 或 FormData
。
错误处理
- 错误的
Content-Type
可能导致 HTTP 415(不支持的媒体类型)。 - 文件上传时若手动设置
multipart/form-data
的boundary
,需确保格式正确。
Content-Type
是 HTTP 通信的“语言协议”,正确设置可确保数据解析无误。开发中需根据场景选择类型:
- API 交互 →
application/json
- 表单提交 →
application/x-www-form-urlencoded
- 文件上传 →
multipart/form-data
- 资源加载 → 对应媒体类型(如
image/png
)
3.3 success参数
在uniapp中,uni.request 方法的 success 参数用于处理请求成功的回调,其写法灵活多样,可以根据开发场景和编码风格选择。以下是常见的几种写法及详细说明:
3.3.1 基础回调函数写法
匿名函数(直接定义)
直接在 success
属性中定义匿名函数,适用于简单逻辑:
uni.request({
url: 'https://api.example.com/data',
success: function(res) {
console.log('请求成功:', res.data); // 输出响应数据
// 其他逻辑处理
},
fail: (err) => { console.error('请求失败:', err); }
});
特点:代码直观,适合快速开发或单次请求场景
注意:res
参数包含 data
(响应体)、statusCode
(HTTP状态码)、header
(响应头)等属性
箭头函数(ES6语法)
使用箭头函数简化作用域绑定,避免 this
指向问题
uni.request({
url: 'https://api.example.com/data',
success: (res) => {
this.dataList = res.data; // 直接访问组件实例的data
}
});
适用场景:Vue 组件中需要访问 this
时推荐使用
3.3.2 分离函数(外部定义)
将回调逻辑抽离为独立函数,提升代码复用性
function handleSuccess(res) {
if (res.statusCode === 200) {
console.log('数据:', res.data);
} else {
console.error('服务器异常:', res.statusCode);
}
}
uni.request({
url: 'https://api.example.com/data',
success: handleSuccess // 传入函数引用
});
- 优点:逻辑清晰,便于复用和单元测试。
- 扩展:可结合错误状态码统一处理(如 401 跳转登录页)
三、Promise
3.1 是什么
Promise 是 JavaScript 中用于处理异步操作的核心对象,它代表一个异步操作的最终完成(或失败)及其结果值。以下是关于 Promise 的详细解析,综合了多个搜索结果中的核心信息。
Promise 是一个容器,用于封装异步操作(如网络请求、定时器、文件读取等),并通过状态管理来追踪其完成情况。它允许开发者以更清晰的方式处理异步逻辑,避免传统的“回调地狱”。
3.2 核心思想
延迟绑定:Promise 将异步操作的结果与处理逻辑分离,通过链式调用(.then()、.catch())注册回调函数,而非嵌套回调。
状态不可逆:Promise 的状态一旦改变(从 pending
到 fulfilled
或 rejected
),便不可再变。
3.3 Promise 的三种状态
Pending(进行中)
初始状态,表示异步操作尚未完成。
Fulfilled(已成功)
异步操作成功完成,通过 resolve(value)
触发,此时 .then()
的回调函数会被执行。
Rejected(已失败)
异步操作失败,通过 reject(error)
触发,此时 .catch()
或 .then()
的第二个参数会捕获错误。
3.4 基本用法与语法
3.4.1 创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作(如 setTimeout、AJAX)
if (/* 成功 */) resolve(value); // 状态变为 fulfilled
else reject(new Error('失败原因')); // 状态变为 rejected
});
例如:
return new Promise((resolve, reject) => {
uni.request({
...options,
success: (res) => {
if(res.data.code === 1) {
resolve(res.data);
}else{
// 统一错误处理(如token过期)
if(res.data.code === -1 || res.data.code === -3) {
uni.navigateTo({url:'/pages/login/login'})
}
reject(res.data);
}
},
fail: (err) => {
uni.showToast({ title: '网络错误', icon: 'none' });
reject(err);
}
})
})
3.4.2 处理结果
.then()
:处理成功状态,接收resolve
传递的值
promise.then((result) => console.log(result));
.catch()
:处理失败状态,捕获reject
或代码抛出的错误
promise.catch((error) => console.error(error));
.finally()
:无论成功或失败都会执行,常用于清理逻辑
3.4.3 链式调用
Promise 的 .then()
返回一个新的 Promise,支持连续调用:
fetchData()
.then(processData)
.then(saveData)
.catch(handleError);
四、封装
我们使用Promise来封装请求。具体的,先给出结果,然后再一点点讲。
定义一个request.js文件
名字不重要,叫什么都可以,目的是通过Promise封装我们的uni.request函数。
import {BASE_URL} from '@/common/config.js'
export const myRequest = (options) => {
// 自动拼接请求地址
options.url = BASE_URL + options.url;
// 默认请求头
options.header = {
'Authorization': uni.getStorageSync('token') || '',
'Content-Type': 'application/json',
...options.header // 允许调用者覆盖
};
//
return new Promise((resolve, reject) => {
uni.request({
...options,
success: (res) => {
if(res.data.code === 1) {
resolve(res.data);
}else{
// 统一错误处理(如token过期)
if(res.data.code === -1 || res.data.code === -3) {
uni.navigateTo({url:'/pages/login/login'})
}
reject(res.data);
}
},
fail: (err) => {
uni.showToast({ title: '网络错误', icon: 'none' });
reject(err);
}
})
})
}
解读:
在这个js文件中,我们通过ES6 模块化规范的导入语法,导入了根目录下的common包中的config.js文件中的BASE_URL。这样做的目的是通过某一个配置文件来集中定义一些通用的配置,方便修改和维护。
config.js
// common/config.js
export const BASE_URL = 'http://106.75.224.22:8090'; // 基础域名
api统一封装
import {myRequest} from '@/utils/request.js'
export const loginApi = (username, password) => {
return myRequest({
url: '/api/login',
method: 'POST',
data: {username, password}
})
};
export const getStatisticApi = (date) => {
const params = date ? { date } : {};
return myRequest({
url: '/api/keepAccount/getStatistic',
method: 'GET',
data: params,
})
};
export const addAccountApi = (event, money, type, happenDate) => {
return myRequest({
url: '/api/keepAccount/addAccount',
method: 'POST',
data: {
event,
money,
type,
happenDate
}
})
};
export const getListApi = (date) => {
return myRequest({
url: '/api/keepAccount/getList',
method: 'GET',
data: {date}
})
};
五、调用演示
handleLogin() {
if (!this.canLogin) return
// 这里替换为实际的登录逻辑
uni.showLoading({
title: '登录中...'
})
// 调用登录接口
loginApi(this.username, this.password)
.then(res => {
uni.showToast({
title: '登录成功',
icon: 'success'
});
// 保存到本地token
// 注意这里为什么不是res.data.data。是因为我们request.js封装的时候,返回的res对象其实是res.data,而非最初的res对象。
uni.setStorageSync("token", res.data);
setTimeout(() => {
uni.reLaunch({
url: '/pages/index/index'
});
}, 1500);
})
.catch(error => {
console.error('登录失败:', error);
uni.showToast({
title: error.msg,
icon: 'none'
});
})
.finally(() => {
uni.hideLoading();
});
console.log("你好");
}
在这个handleLogin()方法中,控制台会先打印“你好”,然后才会处理请求的返回结果。
因为它是同步代码,而 Promise 回调是异步的微任务。
网络请求的响应时间不确定,但其回调一定在同步代码之后执行。
如果需要强制等待请求完成再执行后续逻辑,需使用 async/await
改写:
async handleLogin() {
console.log("你好"); // 仍会先执行
await loginApi(this.username, this.password).then(/* ... */);
console.log("请求完成后才执行"); // 微任务之后
}
怎么样,现在是不是清楚整个封装流程了?🫠
如果有什么疑问或者表述错误地方,也欢迎小伙伴们一起沟通、交流和指正~~~