uni-app 请求封装

一、封装目的

在 uni-app 项目中,接口请求频繁且业务逻辑复杂。

如果每次都直接调用 uni.request,会遇到以下问题:

  • ❌ 每个页面都要手动配置 BaseURL、Header 等公共参数;

  • ❌ 不能统一处理登录过期、token 校验等逻辑;

  • ❌ 无法优雅地控制 loading 状态(并发、闪烁问题);

  • ❌ 错误提示风格不一致;

  • ❌ 重复代码过多,维护成本高。

为了提升开发体验与可维护性,我们对 uni.request 进行二次封装,

实现以下功能:

功能项 说明
全局默认配置 统一 BaseURL、超时时间、请求头
请求/响应拦截器 统一 token 注入与响应处理
登录过期处理 自动跳转登录页、清除缓存
loading 控制 并发请求只显示一个 loading,避免闪烁
自定义实例 可快速创建带不同配置的请求实例
GET/POST 快捷方法 简化调用形式

二、代码结构概览

文件结构建议如下:

复制代码
/utils
 ├─ env.js              // 环境配置(开发/生产)
 ├─ request.js          // 本封装的核心文件

env.js 示例

复制代码
export default {
  baseURL: 'http://123.56.125.118:8300'
};

三、核心实现逻辑

1️⃣ 默认配置管理

复制代码
defaultProps = {
  BaseURL: env.baseURL,
  timeout: 10000,
  header: {},
  isLoading: true, // 是否显示loading
};
  • BaseURL:项目全局接口地址,统一管理。

  • timeout:请求超时时间(毫秒)。

  • header:默认请求头,可在实例化时追加。

  • isLoading:是否在该请求中显示加载提示,可在单个请求中控制。


2️⃣ 请求与响应拦截器

请求拦截(request)

在每次发起请求前自动携带 token。

复制代码
request: (options) => {
  let token = uni.getStorageSync('token');
  if (token) options.header['token'] = token;
  return options;
}

📘 意义

无需在每个请求中手动写入 token,实现统一认证。


响应拦截(response)

对后端返回的数据进行集中处理。

复制代码
response: (res) => {
  if (res.isSuccess) {
    let code = res.data.code;
    switch (code) {
      case 200:
        return res.data;
      case 208:
      case 401:
        return this.timeoutHandler(res.data);
      default:
        uni.showToast({
          title: '程序出现错误',
          icon: 'none'
        });
    }
  } else {
    uni.showToast({
      title: '请求失败',
      icon: 'none'
    });
    return Promise.reject(res);
  }
}

📘 意义

统一处理后端业务状态码,例如:

  • 200:成功返回;

  • 401 / 208:登录过期,触发跳转;

  • 其他状态:友好提示。


3️⃣ Loading 并发控制与防闪烁

核心机制:

复制代码
// 当前进行中的请求数量
loadingCount = 0;

// 防止连续请求时闪烁
timer = null;
逻辑说明
场景 行为
第一个请求开始 显示 Loading
并发多个请求 loadingCount 递增,不重复显示
每个请求完成 loadingCount 递减
所有请求结束 延迟 400ms 后隐藏 Loading(防止闪烁)
实现代码
复制代码
if (requestOptions.isLoading) {
  if (this.timer) {
    clearTimeout(this.timer);
    this.timer = null;
  }
  this.loadingCount++;
  if (this.loadingCount === 1) {
    uni.showLoading({ title: '加载中', mask: true });
  }
}

并在请求的 complete 回调中计数递减:

复制代码
complete: () => {
  if (requestOptions.isLoading) {
    this.loadingCount = Math.max(this.loadingCount - 1, 0);
    if (this.loadingCount === 0) {
      this.timer = setTimeout(() => {
        uni.hideLoading();
        this.timer = null;
      }, 400);
    }
  }
}

📘 意义

解决了「并发多次 loading」与「连续请求闪烁」两大问题,

提升交互流畅度。


4️⃣ 登录过期自动处理

复制代码
timeoutHandler(res) {
  uni.clearStorageSync();
  uni.navigateTo({ url: '/pages/login/index' });
  uni.showToast({
    title: '登录过期,请重新登录',
    icon: 'none',
  });
  return res;
}

📘 意义

后端返回 401/208 时,自动清空缓存并跳转登录页,

无需在业务层单独判断。


5️⃣ 提供简洁调用方法

复制代码
get(options) {
  return this.request({ ...options, method: 'GET' });
}

post(options) {
  return this.request({ ...options, method: 'POST' });
}

📘 意义

简化业务层调用,如:

复制代码
WxRequest.get({ url: '/user/info' });
WxRequest.post({ url: '/login', data: form });

6️⃣ 自定义实例化

复制代码
const customInstance = (options) => {
  return new WxRequestClass(options);
};

📘 意义

当你需要访问不同后端服务或配置(如文件上传、mock 服务)时:

复制代码
const uploadRequest = customInstance({
  BaseURL: 'http://upload.api.com',
  header: { 'Content-Type': 'multipart/form-data' }
});

四、使用示例

1️⃣ 基础 GET 请求

复制代码
WxRequest.get({
  url: '/user/profile',
}).then(res => {
  console.log('用户资料', res);
});

2️⃣ POST 请求(带参数)

复制代码
WxRequest.post({
  url: '/login',
  data: {
    username: 'admin',
    password: '123456'
  }
});

3️⃣ 禁止 loading 的请求

复制代码
WxRequest.get({
  url: '/home/banner',
  isLoading: false // 不显示 loading
});

4️⃣ 并发请求示例

复制代码
WxRequest.all(
  WxRequest.get({ url: '/user/info' }),
  WxRequest.get({ url: '/user/setting' })
).then(([info, setting]) => {
  console.log(info, setting);
});

📘 效果

两个请求同时发起,只显示一个"加载中",

等两个请求都完成后再隐藏。

完整代码

复制代码
import env from './env.js';

class WxRequestClass {
	// 存放一些默认配置:根地址、超时时间、请求头,这些属于全局配置一般情况下是不会改变的
	defaultProps = {
		BaseURL: env.baseURL,
		timeout: 10000,
		header: {},
		isLoading: true, // 是否显示loading效果
	};

	// 用来解决高并发请求时,从开始请求显示loading,到全部完成隐藏loading
	loadingCount = 0;
	// 解决连续请求时,看到的闪烁loading问题 (相当于防抖)
	timer = null;
	// 配置请求、响应拦截器
	interceptor = {
		request: (options) => {
			let token = uni.getStorageSync('token');
			if (token) {
				options.header['token'] = token;
			}
			return options;
		},
		response: (res) => {
			if (res.isSuccess) {
				let code = res.data.code;
				switch (code) {
					case 200:
						return res.data;
					case 208:
						return this.timeoutHandler(res.data);
					case 401:
						return this.timeoutHandler(res.data);
					default:
						// return uni.showToast({
						//   title: '程序出现错误,正在修复',
						//   icon: 'none',
						//   duration: 2000,
						// });
				}
			} else {
				uni.showToast({
					title: '请求失败',
					icon: 'none',
					duration: 2000,
				});
				return Promise.reject(res);
			}
		},
	};
	constructor(defaultProps1) {
		if (defaultProps1) {
			this.defaultProps = Object.assign({}, this.defaultProps, defaultProps1);
		}
	}
	request(options) {
		let requestOptions = Object.assign({}, this.defaultProps, options);
		requestOptions.url = requestOptions.BaseURL + (options.url.startsWith('/') ? options.url : '/' + options.url);
		let interceptorRequest = this.interceptor.request(requestOptions);

		if (requestOptions.isLoading) {
			if (this.timer) {
				clearTimeout(this.timer);
				this.timer = null;
			}
			this.loadingCount++;
			console.log(this.loadingCount, 'this.loadingCount当前数量');
			if (this.loadingCount === 1) {
				uni.showLoading({
					title: '加载中',
					mask: true,
				});
			}
		}

		return new Promise(async (resolve, reject) => {
			try {
				uni.request({
					...interceptorRequest,
					success: (res) => {
						let interceptorResponse = this.interceptor.response({
							...res,
							config: interceptorRequest,
							isSuccess: true
						});
						resolve(interceptorResponse);
					},
					fail: (err) => {
						let interceptorResponse = this.interceptor.response({
							...err,
							config: interceptorRequest,
							isSuccess: false
						});
						reject(interceptorResponse);
					},
					complete: () => {
						if (requestOptions.isLoading) {
							this.loadingCount--;
							if (this.loadingCount === 0) {
								this.timer = setTimeout(() => {
									uni.hideLoading();
									this.timer = null;
								}, 400);
							}
						}
					},
				});
			} catch (error) {
				reject(error);
			}
		});
	}
	// get 请求
	get(options) {
		let requestOptions = Object.assign({}, options, {
			method: 'GET',
		});
		return this.request(requestOptions);
	}

	// post 请求
	post(options) {
		let requestOptions = Object.assign({}, options, {
			method: 'POST',
		});
		return this.request(requestOptions);
	}

	// 处理并发请求
	all(...promises) {
		return Promise.all(promises);
	}

	// 处理登录过期
	timeoutHandler(res) {
		uni.clearStorageSync();
		uni.navigateTo({
			url: '/pages/login/index',
		});
		uni.showToast({
			title: '登录过期,请重新登录',
			icon: 'none',
			duration: 2000,
		});
		return res;
	}
}

// 实例化
let WxRequest = new WxRequestClass();

// 用来自定义配置,想怎么搞就怎么搞
const customInstance = (options) => {
	return new WxRequestClass(options);
};
export {
	customInstance
};

export default WxRequest;

五、封装优点总结

功能点 说明
✅ 全局统一配置 BaseURL、超时时间、请求头集中管理
✅ 自动 token 注入 登录状态自动携带
✅ 登录过期处理 自动跳转、提示用户
✅ 并发 loading 管理 多请求只显示一个 loading
✅ 防闪烁机制 连续请求无闪烁
✅ 支持无 loading 请求 灵活可控
✅ 支持多实例配置 方便扩展不同服务场景
相关推荐
excel5 小时前
Vue 编译器核心 AST 类型系统与节点工厂函数详解
前端
excel5 小时前
Vue 编译器核心:baseCompile 源码深度解析
前端
fakaifa5 小时前
XYcourse课程预约小程序源码+uniapp前端 全开源+搭建教程
uni-app·php·源码分享·源码下载·xycourse·课程预约小程序
excel5 小时前
Vue 编译核心:transformMemo 源码深度解析
前端
excel5 小时前
Vue 编译核心:transformModel 深度解析
前端
excel5 小时前
Vue 编译器源码精解:transformOnce 的实现与原理解析
前端
前端架构师-老李5 小时前
React中useContext的基本使用和原理解析
前端·javascript·react.js
Moonbit5 小时前
招募进行时 | MoonBit AI : 程序语言 & 大模型
前端·后端·面试
excel5 小时前
Vue 3 编译器源码深度解析:transformOn —— v-on 指令的编译过程
前端