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

uni-app H5端刷新后Token丢失的根本原因
H5端页面刷新会重建整个 JavaScript 运行环境,localStorage 本身不会清空,但如果你没在应用启动时主动读取并恢复 Token,uni.getStorageSync('token') 返回的仍是初始值(比如空字符串或 undefined),后续请求自然就 401 了。这不是存储失效,而是“没取出来用”。
常见错误现象包括:
- 登录成功后跳转页面,F5 刷新直接回到登录页
- 接口返回
401 Unauthorized,但控制台查uni.getStorageSync('token')确实有值 - Vue Router 导航守卫里没做 token 检查,或检查逻辑写在了组件
onLoad而非全局入口
必须在 App.vue 的 onLaunch 和 onShow 中恢复 Token
uni-app 的 H5 生命周期里,onLaunch 只在首次加载触发,onShow 在每次页面可见时触发(含刷新)。仅靠 onLaunch 不够,刷新后必须靠 onShow 补救。
实操建议:
- 在
App.vue的onLaunch和onShow中都调用统一函数,例如restoreToken() -
restoreToken内部先读uni.getStorageSync('token'),再设置到请求拦截器(如uni.addInterceptor)和全局状态(如uni.$u.store或Pinia) - 避免在
main.js里直接读取——H5 端main.js执行早于onLaunch,此时uniAPI 尚未就绪
// App.vue
export default {
onLaunch() {
this.restoreToken()
},
onShow() {
this.restoreToken()
},
methods: {
restoreToken() {
const token = uni.getStorageSync('token')
if (token) {
// 设置到请求头
uni.addInterceptor('request', {
invoke(options) {
options.header = {
...options.header,
Authorization: `Bearer ${token}`
}
}
})
// 同步到 Pinia store(如果用了)
this.$store.commit('setToken', token)
}
}
}
}
别用 uni.setStorage 替代 uni.setStorageSync
H5 端 uni.setStorage 是异步 API,而登录后立即跳转、或刷新后立刻发请求,极易出现“刚存完还没写入,就读出来是空”的竞态问题。所有 Token 的读写操作必须同步。
关键区别:
-
uni.setStorageSync('token', 'xxx')→ 立即生效,无回调,适合关键凭证 -
uni.setStorage({ key: 'token', data: 'xxx' })→ 异步,H5 下实际走的是localStorage.setItem包装,但加了 Promise 层,没必要绕路 - 同理,只用
uni.getStorageSync,不用uni.getStorage
注意 H5 端 localStorage 的同源限制和隐私模式兼容性
H5 页面的 localStorage 严格遵循同源策略:协议 + 域名 + 端口完全一致才可共享。开发时 localhost:8080 和 127.0.0.1:8080 是不同源,Token 不互通;生产环境也要确保前后端域名统一。
容易被忽略的点:
- Safari 隐私浏览模式下,
localStorage会被禁用,uni.getStorageSync返回null,需降级处理(如弹提示或强制重新登录) - 某些 Android WebView(尤其旧版)对
localStorage容量敏感,Token 过长(如带大量 payload 的 JWT)可能写入失败,建议截断或改用cookie(需后端配合) - 不要把敏感信息明文存在
localStorage,Token 至少应设 HttpOnly=false + Secure=true(H5 端无法控制 HttpOnly,但后端发 Token 时可加 Secure 标志)
onShow 阶段的补救,以及异步存储引发的时序错乱。











