uni-app 系列(四):网络请求封装与 Pinia 状态管理
2026年8月4日 · 17 分钟
一、uni.request:跨端统一的网络 API
uni-app 用 uni.request 统一各端网络请求,底层在微信端映射 wx.request、H5 端用 XHR/fetch、App 端走原生网络。基本用法基于回调:
uni.request({
url: "https://api.example.com/items",
method: "GET",
data: { page: 1 },
header: { "Content-Type": "application/json" },
success: (res) => { console.log(res.statusCode, res.data); },
fail: (err) => { console.error(err); },
});
裸用回调在业务里会写得很啰嗦,所以实际项目一定要封装。
二、封装一个生产级请求层
目标:Promise 化 + 自动注入 token + 统一响应解包 + 统一错误处理 + 401 跳登录。
// utils/request.js
const BASE_URL = "https://api.example.com";
export function request({ url, method = "GET", data, auth = true }) {
const header = { "Content-Type": "application/json" };
// 自动注入 token
if (auth) {
const token = uni.getStorageSync("token");
if (token) header.Authorization = "Bearer " + token;
}
return new Promise((resolve, reject) => {
uni.request({
url: BASE_URL + url,
method,
data,
header,
success: (res) => {
// 统一处理登录失效
if (res.statusCode === 401) {
uni.removeStorageSync("token");
uni.navigateTo({ url: "/pages/login/login" });
return reject(new Error("未登录"));
}
// 后端统一响应体 { code, message, data }
const body = res.data;
if (body.code === 0) {
resolve(body.data); // 只把业务数据抛给调用方
} else {
uni.showToast({ title: body.message, icon: "none" });
reject(new Error(body.message));
}
},
fail: (err) => {
uni.showToast({ title: "网络异常", icon: "none" });
reject(err);
},
});
});
}
// 语义化封装
export const http = {
get: (url, data, auth) => request({ url, method: "GET", data, auth }),
post: (url, data, auth) => request({ url, method: "POST", data, auth }),
};
业务里就非常清爽:
import { http } from "@/utils/request";
const list = await http.get("/items", { page: 1 });
关于"拦截器"
uni-app 提供 uni.addInterceptor 可对 request、navigateTo 等 API 做拦截。但实践中,自己封装 request 函数(如上)比用 addInterceptor 更灵活可控,是主流做法。要点齐活:请求前注入 token、响应后统一解包、失败统一提示、401 统一跳登录。
三、本地存储
uni.setStorageSync("token", "abc"); // 同步存
const t = uni.getStorageSync("token"); // 同步取
uni.removeStorageSync("token"); // 删
uni.clearStorageSync(); // 清空
// 也有异步版:uni.setStorage / getStorage
注意各端容量限制:微信小程序单 key 上限 1MB、总上限 10MB。别拿它当数据库,只存 token、少量用户配置、缓存标记等。
四、状态管理:为什么用 Pinia
跨页面共享的数据(登录态、购物车、全局配置)应放状态管理库。uni-app(Vue3)官方推荐 Pinia,原因:
- Vue3 官方推荐,取代 Vuex,是 Vue 生态的新标准;
- API 更简洁:没有 mutations,直接改 state / 用 actions;
- 完整 TypeScript 支持,类型推导好;
- 模块化天然:每个 store 独立,无需嵌套 modules。
定义一个 store
// stores/user.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useUserStore = defineStore("user", () => {
const token = ref("");
const profile = ref(null);
const isLogin = computed(() => !!token.value);
function setSession(session) {
token.value = session.token;
profile.value = session.user;
uni.setStorageSync("token", session.token);
}
function logout() {
token.value = "";
profile.value = null;
uni.removeStorageSync("token");
}
return { token, profile, isLogin, setSession, logout };
});
在 main.js 注册
import { createSSRApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
export function createApp() {
const app = createSSRApp(App);
app.use(createPinia());
return { app };
}
在页面/组件里用
import { useUserStore } from "@/stores/user";
const user = useUserStore();
if (user.isLogin) { /* ... */ }
user.setSession(session);
持久化:Pinia 的 state 存在内存里,App/小程序被杀进程后会丢。登录态这类要持久的数据,需要在
setSession里同步写uni.setStorageSync,并在应用启动(App.vue的onLaunch)时从 storage 读回来 restore。也可用持久化插件,但小程序端需注意兼容。
五、本阶段必须掌握的知识点
-
uni.request基本用法,以及为什么/如何封装成 Promise 化请求层。 - 请求层五要素:token 注入、响应解包、统一错误提示、401 跳登录、BASE_URL 管理。
- 本地存储 API 与容量限制,明确它只存轻量数据。
- Pinia 优于 Vuex 的理由、store 的组合式写法、在 main.js 注册、页面中使用。
- Pinia 状态不持久,需配合 storage 做登录态持久化与启动 restore。
六、高频面试题
Q1:如何封装 uni-app 的网络请求?要考虑哪些点?
A:核心是把回调式的 uni.request 包成返回 Promise 的函数,并集中处理横切关注点:① 统一 BASE_URL 与请求头;② 请求前自动注入 token(从 storage 取);③ 响应后按后端约定的 {code,message,data} 统一解包,只把业务 data 交给调用方;④ 统一错误提示(uni.showToast)与 reject;⑤ 对 401 登录失效统一清 token 并跳登录页。再对外暴露 get/post 等语义化方法,业务层就能 await http.get(...)。
追问:uni-app 有 addInterceptor,为什么很多人还是手写封装?
A:uni.addInterceptor 能拦截 request/路由等 API,但它的能力和可读性不如自己封装函数灵活——手写封装能精确控制解包逻辑、按接口区分是否鉴权、做重试/取消/并发控制等,且调试直观。实际项目里以自封装 request 为主,addInterceptor 更多用于给路由跳转加全局埋点/登录守卫这类场景。
Q2:uni-app 项目里状态管理选 Vuex 还是 Pinia?为什么?
A:Vue3 项目推荐 Pinia。原因:① 它是 Vue 官方新推荐、取代 Vuex;② API 更简洁,去掉了 mutations,可直接修改 state 或通过 actions;③ TypeScript 支持完善,类型推导友好;④ 天生模块化,每个 store 独立,无 Vuex 的 modules 嵌套心智负担。老项目若已用 Vuex 可继续,但新项目用 Pinia。
追问:Pinia 的数据在小程序里会持久保存吗?怎么做登录态持久化?
A:不会。Pinia 的 state 在内存中,小程序/App 进程被回收或用户杀掉后就丢失。登录态要持久化,需在写入 store 的同时用 uni.setStorageSync 落盘,并在 App.vue 的 onLaunch 里从 storage 读回来恢复到 store(restore)。也可用 pinia 持久化插件,但要注意小程序端 storage API 的兼容适配。
Q3:uni.setStorageSync 和 uni.setStorage 区别?存储有什么限制?
A:...Sync 是同步版本,直接返回/阻塞,代码顺序执行,适合存 token 这类小数据;不带 Sync 的是异步版本,通过 success/fail 回调。限制:以微信小程序为例,单个 key 上限 1MB、单个用户总上限 10MB,且是键值对存储,不能当数据库用。存大量结构化数据应走后端或本地数据库方案。
追问:同步存储会不会阻塞影响性能?
A:小数据(token、配置)用同步版几乎无感,方便;但大数据频繁同步读写会阻塞 JS 线程,导致卡顿。大数据或高频场景应改用异步版,或减少读写次数(如内存缓存 + 批量落盘)。