为什么要状态管理
多页面应用里,登录状态、用户信息、购物车等数据需要全局共享。uni-app 的 Vue3 版本推荐使用 Pinia(Vue 官方状态库),用法和 Web 端一致。
一、安装与初始化
main.js 注册:
1 2 3 4 5 6 7 8 9 10
| 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 } }
|
二、定义 user store:登录状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', { state: () => ({ token: uni.getStorageSync('token') || '', userInfo: null }),
getters: { isLoggedIn: (state) => !!state.token },
actions: { setToken(token) { this.token = token uni.setStorageSync('token', token) },
async login(username, password) { const res = await request({ url: '/api/login', method: 'POST', data: { username, password } }) this.setToken(res.token) this.userInfo = res.user return res },
logout() { this.token = '' this.userInfo = null uni.removeStorageSync('token') uni.reLaunch({ url: '/pages/login/login' }) } } })
|
页面中使用:
1 2 3 4 5 6
| <script setup> import { useUserStore } from '@/stores/user'
const userStore = useUserStore() console.log('是否登录:', userStore.isLoggedIn) </script>
|
三、统一请求封装:拦截器 + token + 错误处理
一个带拦截器的 request,是跨端项目的标配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| const BASE_URL = 'https://api.example.com'
export function request(options) { return new Promise((resolve, reject) => { const token = uni.getStorageSync('token')
uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), ...options.header },
success: (res) => { if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data) } else if (res.statusCode === 401) { uni.removeStorageSync('token') uni.reLaunch({ url: '/pages/login/login' }) reject(new Error('登录已过期')) } else { uni.showToast({ title: res.data.message || '请求失败', icon: 'none' }) reject(new Error(res.data.message || '请求失败')) } },
fail: (err) => { uni.showToast({ title: '网络异常,请检查网络', icon: 'none' }) reject(err) } }) }) }
|
为什么封装成 Promise:uni.request 是回调式 API,封装后页面里可以 await,配合 async/await 让代码和 Web 端一样清爽:
1 2 3 4 5 6 7 8 9
| async function loadOrders() { uni.showLoading({ title: '加载中' }) try { const list = await request({ url: '/api/orders' }) orders.value = list } finally { uni.hideLoading() } }
|
四、组合使用:登录 → 存状态 → 跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <script setup> import { ref } from 'vue' import { useUserStore } from '@/stores/user'
const userStore = useUserStore() const username = ref('') const password = ref('') const loading = ref(false)
async function handleLogin() { if (!username.value || !password.value) { uni.showToast({ title: '请输入账号密码', icon: 'none' }) return } loading.value = true try { await userStore.login(username.value, password.value) uni.showToast({ title: '登录成功', icon: 'success' }) setTimeout(() => uni.switchTab({ url: '/pages/index/index' }), 500) } catch (e) { // 错误提示已在 request 里统一处理 } finally { loading.value = false } } </script>
|
五、跨端差异注意
- H5 端可以 localStorage,小程序不行:统一用
uni.setStorageSync/getStorageSync,别混用;
uni.request 的 header 区分大小写:Content-Type 各端表现可能不同,建议按上面示例统一设置;
- 小程序合法域名:正式发布必须在微信公众平台配置 request 合法域名,否则请求全挂;
- Pinia 在跨端:Vue3 版 uni-app 完全兼容 Pinia,Vue2 版则用 Vuex。
六、常见坑
uni.showLoading 没 hide:loading 弹窗会一直挂着,用 try/finally 保证隐藏;
- 401 重复弹窗:多个请求同时 401 会跳多次登录页,可加”是否正在跳转”标记去重;
- token 过期但接口继续打:请求拦截器里 401 直接清 token 并中断,别让后续请求带着旧 token 继续打。
总结
跨端项目的状态管理套路:Pinia 管全局状态(登录态持久化到 storage)→ 统一 request 封装(token 注入、401 处理、错误提示)→ 页面里 async/await 消费。这套组合在 H5、微信小程序、App 端行为一致,是 uni-app 项目的标准架构。