根本原因是刷新后未在onshow中恢复token,导致请求无认证头而401;需在app.vue的onlaunch和onshow中同步读取localstorage并注入请求拦截器和全局状态。

uni-app H5刷新后Token丢失的根本原因
不是 localStorage 被清空,而是 uni-app 启动后没主动读取并注入 Token。H5 刷新会重建 JS 运行环境,uni.getStorageSync('token') 的值还在,但没人把它塞进请求拦截器、全局状态或 axios 实例里,后续所有请求都因缺 Authorization 头而 401。
常见现象包括:登录后 F5 刷新直接跳回登录页;控制台查 uni.getStorageSync('token') 确实有值,但接口仍 401;Vue Router 守卫里判断了 token 却始终走不到逻辑分支——因为守卫执行时,token 还没被 restore。
必须在 App.vue 的 onLaunch 和 onShow 中同步恢复 Token
H5 的 onLaunch 只触发一次(首次加载),onShow 在每次页面可见时触发(含刷新、切后台再切回、第三方回调跳转后)。只靠 onLaunch 不够,刷新后必须靠 onShow 补救。
实操建议:
- 在
App.vue中定义统一函数restoreToken(),内部先读uni.getStorageSync('token'),再设置到请求拦截器(如uni.addInterceptor)和全局状态(如uni.$u.store或 Pinia) - 不要在
main.js里读取 token —— H5 端main.js执行早于onLaunch,此时uniAPI 尚未就绪,调用会静默失败 - 避免在组件
onLoad或onShow中恢复 token —— 多个页面同时触发会造成重复设置,且无法覆盖全局请求拦截器
请求拦截器中如何安全注入 Authorization 头
如果只在拦截器里写 config.header.Authorization = uni.getStorageSync('token'),刷新后第一次请求仍会漏掉,因为拦截器初始化早于 restoreToken() 执行。
正确做法是让拦截器“懒读取”:
- 拦截器内不直接读取 storage,而是从一个响应式变量(如
uni.$u.token或 Pinia store 的token字段)取值 -
restoreToken()函数负责更新这个变量,确保拦截器每次发起请求时拿到的是最新值 - 对空值做防御:若变量为空,可跳转登录或抛出错误,避免发无认证请求
示例片段(Pinia 场景):
// store/auth.js
export const useAuthStore = defineStore('auth', {
state: () => ({
token: uni.getStorageSync('token') || ''
}),
actions: {
setToken(t) {
this.token = t
uni.setStorageSync('token', t)
}
}
})
// 请求拦截器中
uni.addInterceptor({
invoke(config) {
const authStore = useAuthStore()
if (authStore.token) {
config.header.Authorization = authStore.token
}
}
})
为什么不能依赖 Vuex 默认持久化
默认的 Vuex 是纯内存态,刷新即清零。即使你把 token 存进 Vuex,不配持久化插件,它照样丢。
必须显式集成 vuex-persistedstate 并配置 key 映射:
- 仅靠
createPersistedState()默认行为不够,需指定 storage 为uni.setStorageSync/uni.getStorageSync,否则仍走浏览器原生localStorage,在某些安卓 WebView 下可能不可靠 - 推荐组合:Vuex +
vuex-persistedstate+ 自定义 storage 方法,再配合App.vue的onShow主动同步,三重保险 - 注意:
vuex-persistedstate恢复时机晚于onLaunch,所以仍需在onLaunch和onShow中手动触发restoreToken(),不能只靠插件
最易被忽略的一点:token 恢复后,要检查是否已过期。别只管存和取,忘了校验有效期——否则用户可能带着过期 token 刷半天接口,直到 401 后才跳登录,体验断层。











