uni-app 网络请求
和浏览器里的 fetch、axios 类似,uni-app 提供 uni.request 来发起 HTTP 请求,而且这个 API 在所有平台行为一致,小程序、App、H5 都走它。
本文从最基础的 GET 请求讲起,到拦截器统一处理,最后做一个完整的列表加载示例。
最基础的 GET 请求
uni.request({
url: 'https://api.example.com/posts',
method: 'GET',
success: (res) => {
console.log('请求成功', res.data)
},
fail: (err) => {
console.log('请求失败', err)
}
})
四个关键点:
url,接口地址method,请求方法,默认 GETsuccess,请求成功回调(HTTP 返回了,但不代表业务成功)fail,请求失败回调(断网、超时、域名不通)
回调方式 vs Promise 方式
uni.request 同时支持回调写法(上面的写法)和 Promise 写法。Vue3 项目推荐 Promise 写法,配合 async/await 代码更清晰:
// 写法一:回调
uni.request({ url, success: (res) => {} })
// 写法二:Promise
const res = await uni.request({ url })
console.log(res.data)
GET 带参数
参数拼在 url 的 query 里,也可以放在 data 中,uni-app 会自动拼到地址上:
// 两种写法等价
uni.request({
url: 'https://api.example.com/search',
data: { keyword: '手机', page: 1 },
success: (res) => {
console.log(res.data)
}
})
POST 带 body
uni.request({
url: 'https://api.example.com/login',
method: 'POST',
data: {
username: 'tom',
password: '123456'
},
success: (res) => {
console.log(res.data)
}
})
POST 时 data 会被序列化,需要注意 header 的设置,微信小程序要求 Content-Type 为 application/json 时数据必须手动转 JSON 字符串,所以最常见的写法是:
uni.request({
url: 'https://api.example.com/login',
method: 'POST',
data: JSON.stringify({ username: 'tom', password: '123456' }),
header: { 'Content-Type': 'application/json' },
success: (res) => {
console.log(res.data)
}
})
响应处理与错误处理
HTTP 请求成功(success 触发)不代表业务成功,常见的接口约定是,HTTP 200 返回结构体,code 字段表示业务状态:
{
"code": 0,
"message": "ok",
"data": { "id": 1, "title": "标题" }
}
处理时先看 res.statusCode,再判断 code:
const res = await uni.request({
url: 'https://api.example.com/login',
method: 'POST',
data: JSON.stringify({ username: 'tom', password: '123456' }),
header: { 'Content-Type': 'application/json' }
})
if (res.statusCode !== 200) {
// 网络层异常(4xx、5xx)
uni.showToast({ title: '服务器异常,请稍后重试', icon: 'none' })
return
}
const body = res.data
if (body.code !== 0) {
// 业务层异常(比如密码错误)
uni.showToast({ title: body.message || '请求失败', icon: 'none' })
return
}
// 正常拿到数据
console.log(body.data)
拦截器统一处理
项目里每个请求都要带 token、都要统一处理错误,不可能每个请求写一遍,用 uni.addInterceptor 给 request 加拦截器:
// 建议放在 main.js 或单独的工具文件里,启动时执行一次
uni.addInterceptor('request', {
// 请求发出前,统一注入 token
invoke(args) {
const token = uni.getStorageSync('token')
if (token) {
args.header = { ...args.header, Authorization: token }
}
},
// 请求成功后,统一处理业务错误
success(res) {
if (res.data && res.data.code === 401) {
uni.removeStorageSync('token')
uni.reLaunch({ url: '/pages/login/login' })
}
},
// 请求失败,统一提示
fail(err) {
uni.showToast({ title: '网络异常,请检查网络', icon: 'none' })
}
})
加了拦截器之后,业务代码里的请求就不用再重复处理 token 和错误,只关心数据。
封装请求工具函数
进阶做法是把请求封装成函数,业务层只传数据和拿结果:
// utils/request.js
export function request(options) {
return new Promise((resolve, reject) => {
uni.request({
...options,
success: (res) => {
if (res.statusCode === 200 && res.data.code === 0) {
resolve(res.data.data)
} else {
reject(res.data)
}
},
fail: reject
})
})
}
// 业务代码中使用
import { request } from '@/utils/request.js'
const list = await request({ url: 'https://api.example.com/posts' })
H5 端跨域问题
这是 H5 端特有的坑。H5 页面部署在 localhost:8080,接口在别的域名时,浏览器会拦截跨域请求(CORS),小程序和 App 端没有这个问题。
开发阶段可以用 HBuilderX 的代理配置解决,在 manifest.json 的 H5 配置里添加 devServer 代理:
{
"h5": {
"devServer": {
"proxy": {
"/api": {
"target": "https://api.example.com",
"changeOrigin": true,
"pathRewrite": { "^/api": "" }
}
}
}
}
}
配置后,代码里请求 /api/posts,HBuilderX 会转发到 https://api.example.com/posts,浏览器不再报跨域。
提示:生产环境的跨域需要后端配合(设置 CORS 头)或由网关转发,前端改不了。
综合示例:新闻列表
把组件篇、样式篇的知识全用上,做一个从接口加载并渲染的新闻列表:
<template>
<view class="page">
<view class="loading" v-if="loading">
<text>加载中...</text>
</view>
<view class="news-item" v-for="item in newsList" :key="item.id" @click="onItemClick(item)">
<image class="news-img" :src="item.cover" mode="aspectFill"></image>
<view class="news-info">
<text class="news-title">{{ item.title }}</text>
<text class="news-date">{{ item.date }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
const newsList = ref([])
const loading = ref(false)
async function fetchNews() {
loading.value = true
try {
const res = await uni.request({ url: 'https://api.example.com/news' })
if (res.statusCode === 200) {
newsList.value = res.data.data
}
} catch (e) {
uni.showToast({ title: '加载失败,请检查网络', icon: 'none' })
} finally {
loading.value = false
}
}
onLoad(() => {
fetchNews()
})
// 下拉刷新
onPullDownRefresh(async () => {
await fetchNews()
uni.stopPullDownRefresh()
})
function onItemClick(item) {
uni.navigateTo({ url: `/pages/detail/detail?id=${item.id}` })
}
</script>
<style scoped>
.page {
padding: 20rpx;
}
.loading {
text-align: center;
color: #999;
padding: 40rpx 0;
}
.news-item {
display: flex;
padding: 20rpx;
margin-bottom: 20rpx;
border-radius: 16rpx;
background-color: #fff;
}
.news-img {
width: 200rpx;
height: 140rpx;
border-radius: 12rpx;
}
.news-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.news-title {
font-size: 30rpx;
color: #333;
}
.news-date {
font-size: 24rpx;
color: #999;
}
</style>
这个示例里的数据流是,页面加载 onLoad 发起请求,拿到数据存进 newsList,v-for 渲染列表,点条目跳详情,下拉刷新重新拉取。这已经是真实项目里列表页的标准形态了。
总结
| 知识点 | 要点 |
|---|---|
| 发起请求 | uni.request,所有平台行为一致 |
| 数据传参 | GET 用 data 拼 query,POST 传 JSON 字符串 |
| 错误处理 | 先看 statusCode,再判断业务 code |
| 拦截器 | uni.addInterceptor 统一注入 token、处理错误 |
| 封装 | 封装成 request 函数,业务层只关心数据和结果 |
| H5 跨域 | 开发用 devServer proxy,生产靠后端 CORS |
一句话总结:uni.request 就是跨端的 fetch,配合拦截器统一处理 token 和错误,再封装成工具函数,业务代码里三行就能拿到数据。