六维教程

uni-app 网络请求

和浏览器里的 fetchaxios 类似,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,请求方法,默认 GET
  • success,请求成功回调(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-Typeapplication/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.addInterceptorrequest 加拦截器:

// 建议放在 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 发起请求,拿到数据存进 newsListv-for 渲染列表,点条目跳详情,下拉刷新重新拉取。这已经是真实项目里列表页的标准形态了。

总结

知识点 要点
发起请求 uni.request,所有平台行为一致
数据传参 GET 用 data 拼 query,POST 传 JSON 字符串
错误处理 先看 statusCode,再判断业务 code
拦截器 uni.addInterceptor 统一注入 token、处理错误
封装 封装成 request 函数,业务层只关心数据和结果
H5 跨域 开发用 devServer proxy,生产靠后端 CORS

一句话总结:uni.request 就是跨端的 fetch,配合拦截器统一处理 token 和错误,再封装成工具函数,业务代码里三行就能拿到数据。

上一篇
uni-app 样式开发
下一篇
uni-app 条件编译