UniApp 中 Request 封装与使用全解析(避坑指南)

在 UniApp 项目中,接口请求是不可或缺的一部分。为了提高代码的可维护性和扩展性,合理封装请求至关重要。本文将介绍如何在 UniApp 项目中进行 request 请求的封装与使用,并给出避坑指南,帮助你在开发过程中少走弯路。

一、封装请求的基本思路

为了简化和统一接口调用,我们可以将请求逻辑封装到一个通用方法中。通过封装 request,可以为每次请求自动带上公共的请求头、处理全局错误和请求状态,减少重复代码。

1. 基础配置与环境切换
// 引入必要的工具和模块
import errorCode from "@/utils/errorCode"; // 错误码字典
import store from "@/store"; // 状态管理器,用于全局 loading 控制
import { globalData } from "@/globalData"; // 全局配置
import { md5DigestAsHex } from "@/utils/auth"; // MD5 加密工具

// 根据当前环境切换基础请求地址
let baseURL = globalData.baseUrl;
if (process.env.NODE_ENV === "development") {
	baseURL = "/main"; // 开发环境使用本地代理
}

避坑指南:开发环境和生产环境需要不同的请求地址,避免在开发时直接请求生产接口,导致数据污染。

2. 自定义请求头设置

我们可以根据请求方法类型,动态设置 Content-Type,使请求头适应不同接口的需求。

// 根据请求类型动态设置 Content-Type
function getContentType(options) {
	if (options.method?.toLocaleUpperCase() === "POST" || options.method?.toLocaleUpperCase() === "DELETE") {
		return options.contentType
			? { ["content-type"]: options.contentType } // 如果有传入 contentType 则使用
			: {}; // 默认返回空对象
	} else {
		return {};
	}
}

避坑指南POST 请求的 Content-Type 通常是 application/jsonapplication/x-www-form-urlencoded,而 GET 请求不需要 Content-Type,根据接口文档调整传参格式。

3. request 方法封装

request 方法是核心,它负责发起请求、设置请求头、处理请求的成功和失败。

// 全局请求封装,返回一个 Promise
export const request = (options) => {
	return new Promise((resolve, reject) => {
		store.commit("loading"); // 开启全局 loading 状态

		// 特殊处理:如果是登录验证码接口,清除本地缓存的 token
		if (options.url === "/login/captcha/captcha") {
			uni.removeStorageSync("token");
		}

		uni.request({
			url: baseURL + options.url, // 拼接基础地址和接口地址
			method: options.method || "GET", // 默认请求方法为 GET
			data: options.data || options.params || {}, // 传递的参数
			dataType: options.dataType || "json", // 请求的数据类型,默认为 JSON
			responseType: options.responseType || "text", // 响应的数据类型
			header: {
				devtype: "app", // 自定义请求头
				rid: "tempTokenId-" + md5DigestAsHex(uni.getStorageSync("token")), // 加密 token
				token: uni.getStorageSync("token"), // 从本地缓存中获取 token
				...getContentType(options) // 动态获取 Content-Type
			},
			// 请求成功回调
			success: (res) => {
				const code = res.data.code || 200; // 服务器返回的状态码,默认为 200
				const msg = errorCode[code] || res.data.msg || errorCode["default"]; // 错误信息提示

				if (code == 401) {
					// 处理未授权的情况,弹出提示框并跳转到登录页
					uni.showModal({
						title: "提示",
						content: msg,
						showCancel: false,
						success: (res) => {
							if (res.confirm) {
								uni.reLaunch({ url: "/pages/login/index" });
							}
						}
					});
				} else if (code == 500) {
					// 处理服务器错误
					uni.showToast({
						title: msg,
						icon: "none",
						mask: true,
						duration: 2000
					});
				} else {
					// 特殊处理:如果是登录接口,保存返回的 token
					if (options.url === "/login/captcha/captcha") {
						uni.setStorageSync("token", res.header.token);
					}
					// 根据响应类型返回数据
					resolve(options.responseType === "arraybuffer" ? res : res.data);
				}
			},
			// 请求失败回调
			fail: (err) => {
				console.log(baseURL + options.url, err); // 打印错误日志
				reject(err); // 返回错误信息
			},
			// 请求完成后关闭 loading
			complete() {
				store.commit("loaded");
			}
		});
	});
};

避坑指南

  • 在发起请求前,确保 token 是最新的,否则会导致认证失败。可以在登录接口中更新 token 并存储在本地。
  • 接口响应为 500 错误时,除了简单的 toast 提示,还可以添加更细致的错误处理,比如重试机制。
二、模块化 API 管理

使用封装后的 request 方法,我们可以通过模块化的方式管理项目中的 API。以下是在 @/api/user.js 中封装的两个用户相关接口:

import { request } from "@/common/request.js";

// 获取公钥接口
export function getPublicKey(params) {
	return request({
		url: "/login/getPublicKey?_t=" + new Date().getTime(), // 动态添加时间戳,防止缓存
		method: "get",
		params
	});
}

// 用户登录接口
export function doLogin(data, urlType, temporaryToken) {
	return request({
		url: "/login/dologin", // 登录接口
		method: "post", // POST 请求
		dataType: "json", // 请求的数据类型
		data, // 传递的登录数据
		temporaryToken
	});
}

避坑指南

  • 动态添加时间戳 _t,可以防止 GET 请求被浏览器缓存,确保每次请求都能拉取最新数据。
  • 在登录请求时,确保 data 中传递的字段与后端接口一致,避免因参数格式不符而导致登录失败。
三、完整的 request.js 代码

为了便于参考,以下是完整的 request.js 代码:

import errorCode from "@/utils/errorCode";
import store from "@/store";
import { globalData } from "@/globalData";
import { md5DigestAsHex } from "@/utils/auth";

let baseURL = globalData.baseUrl;
if (process.env.NODE_ENV === "development") {
	baseURL = "/main";
}

function getContentType(options) {
	if (options.method?.toLocaleUpperCase() === "POST" || options.method?.toLocaleUpperCase() === "DELETE") {
		return options.contentType
			? { ["content-type"]: options.contentType }
			: {};
	} else {
		return {};
	}
}

export const request = (options) => {
	return new Promise((resolve, reject) => {
		store.commit("loading");

		if (options.url === "/login/captcha/captcha") {
			uni.removeStorageSync("token");
		}

		uni.request({
			url: baseURL + options.url,
			method: options.method || "GET",
			data: options.data || options.params || {},
			dataType: options.dataType || "json",
			responseType: options.responseType || "text",
			header: {
				devtype: "wxapp",
				rid: "tempTokenId-" + md5DigestAsHex(uni.getStorageSync("token")),
				token: uni.getStorageSync("token"),
				...getContentType(options)
			},
			success: (res) => {
				const code = res.data.code || 200;
				const msg = errorCode[code] || res.data.msg || errorCode["default"];
				if (code == 401) {
					uni.showModal({
						title: "提示",
						content: msg,
						showCancel: false,
						success: function (res) {
							if (res.confirm) {
								uni.reLaunch({ url: "/pages/login/index" });
							}
						}
					});
				} else if (code == 500) {
					uni.showToast({
						title: msg,
						icon: "none",
						mask: true,
						duration: 2000
					});
				} else {
					if (options.url === "/login/captcha

/captcha") {
						uni.setStorageSync("token", res.header.token);
					}
					resolve(options.responseType === "arraybuffer" ? res : res.data);
				}
			},
			fail: (err) => {
				console.log(baseURL + options.url, err);
				reject(err);
			},
			complete() {
				store.commit("loaded");
			}
		});
	});
};
四、总结与避坑要点
  1. 环境切换:确保开发和生产环境的接口地址正确配置,避免数据泄露或污染。
  2. 动态设置请求头:根据不同请求类型灵活调整 Content-Type,确保数据传输格式符合后端要求。
  3. 统一错误处理:将通用错误处理集中管理,确保用户能及时了解请求状态。
  4. Token 管理:定时清理过期 token 并在请求时加密存储,避免认证问题。

通过以上封装,你的 UniApp 项目将变得更加灵活、稳定,同时也减少了重复代码。

上一篇
实战篇:(四)Vue2 + Three.js 创建可交互的360度全景视图,可控制旋转、缩放完整代码
下一篇
实战篇:(六)创建属于自己的 Vue 3 组件库:主题切换与样式管理

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐