Vue Router 页面刷新后怎么处理状态?
做Vue项目时,不少同学会碰到页面刷新后路由相关状态丢失的问题——比如登录态没了、列表数据空了、甚至直接跳404页面,为啥刷新会搞出这些麻烦?不同场景下该怎么解决?今天咱们从原理到实操,把Vue Router刷新那点事儿掰碎了讲。
页面刷新时,Vue Router 发生了什么?
先搞懂SPA(单页应用)的路由逻辑:Vue Router本质是前端路由,靠JS动态切换页面内容,不会真的向服务器发新请求,但刷新页面时,浏览器会“打破”这个逻辑——它会把当前URL当成新的资源请求发给服务器。
举个例子:你做了个博客项目,路由是 /article/123
(history模式),开发时刷新页面,浏览器会直接请求服务器的 /article/123
路径,如果服务器没特殊配置,压根找不到这个资源,就会返回404;就算服务器配置了“ fallback 到index.html”,页面能正常显示,但前端存的状态(比如Vuex里的用户信息、组件里的临时数据)会因为JS重新执行、内存被清空而丢失。
要是用hash模式(URL带 ,/#/article/123
),刷新时浏览器只会请求 前面的路径(比如根路径 ),服务器返回index.html后,前端路由再解析 后的部分,所以hash模式刷新不容易404,但状态丢失的问题(比如Vuex数据清空)还是存在。
常见的刷新后问题有哪些?
刷新后踩的“坑”,本质是前端状态丢失和服务端资源匹配这两类问题,具体表现特典型:
-
登录状态“消失”
比如用Vuex存用户token,刷新后Vuex被重置,token没了,导致页面跳转到登录页,哪怕你把token存在sessionStorage里,Vuex不主动读的话,组件里也拿不到登录态。 -
页面数据需要“重新要”
列表页刚加载完数据,一刷新,数据全没了,得重新调接口,因为组件里的data在刷新后会被重置,之前请求到的数据没了。 -
路由参数/权限逻辑“失效”
比如详情页靠$route.params.id
拿数据,刷新后参数还在,但如果是权限页面(比如管理员页),刷新后没重新验证权限,可能让未授权用户进入。 -
直接跳404页面
用history模式时,服务器没配置“所有路径都返回index.html”,刷新就会因为服务器找不到资源而404。
怎么解决刷新后的状态保持问题?
针对不同问题,有不同的解法,核心思路是“状态持久化”+“服务端配合”+“逻辑兜底”,下面分场景讲实操方法:
方法1:用浏览器存储(localStorage/sessionStorage)+ Vuex 持久化
原理很简单:把Vuex里的关键状态(比如token、用户信息)存到浏览器存储(localStorage长期存,sessionStorage会话内存),页面刷新后,让Vuex重新读存储里的数据,恢复状态。
实操步骤:
-
存状态:登录成功时,既要把token存到Vuex,也要存到sessionStorage,比如在Vuex的mutation里写:
// store/user.js const mutations = { SET_TOKEN(state, token) { state.token = token; sessionStorage.setItem('token', token); // 同时存到sessionStorage } };
-
读状态:页面刷新后,Vue初始化时,让Vuex主动读存储里的token,可以在
main.js
里写:new Vue({ router, store, beforeCreate() { const token = sessionStorage.getItem('token'); if (token) { this.$store.commit('user/SET_TOKEN', token); } }, render: h => h(App) }).$mount('#app');
-
进阶:用插件自动持久化
要是项目大,手动存/读太麻烦,可以用vuex-persistedstate
插件,配置要持久化的模块,它会自动把Vuex状态同步到localStorage/sessionStorage,刷新后自动恢复。安装后配置:
import createPersistedState from 'vuex-persistedstate'; const store = new Vuex.Store({ // ...其他配置 plugins: [createPersistedState({ storage: window.sessionStorage, // 选sessionStorage或localStorage reducer(state) { // 只持久化user模块 return { user: state.user }; } })] });
方法2:服务端渲染(SSR)或静态站点生成(SSG)
如果项目对首屏速度、SEO要求高,或者需要服务端直接处理用户状态,可以用Nuxt.js(Vue的SSR框架)。
原理:刷新时,服务端先拿到请求的URL,渲染对应的页面(包括获取用户会话、接口数据),再把渲染好的HTML发给客户端,这样刷新后,状态能在服务端直接处理,不用依赖前端存储。
适用场景:
- 企业官网、博客等需要SEO的项目;
- 对首屏加载速度敏感的应用(比如电商首页)。
缺点:
- 复杂度高,需要学习Nuxt.js的生态和服务端部署;
- 服务器资源消耗比纯SPA大。
方法3:路由守卫里处理刷新逻辑
路由守卫(router.beforeEach
)能在“页面跳转/刷新后进入页面”前拦截,适合处理权限验证、数据预加载。
例子:刷新后验证登录态
全局前置守卫里,判断用户是否登录,没登录就跳登录页:
// router.js router.beforeEach((to, from, next) => { const isLogin = !!sessionStorage.getItem('token'); // 从sessionStorage拿token if (to.meta.requiresAuth && !isLogin) { next('/login'); // 需要登录但没登录,跳登录页 } else { next(); // 放行 } });
路由配置里给需要权限的页面加meta:
{ path: '/admin', name: 'Admin', component: Admin, meta: { requiresAuth: true } // 标记需要登录 }
例子:刷新后预加载数据
比如用户进入订单列表页,刷新后要重新拉取订单数据,可以在路由守卫里发请求:
router.beforeEach(async (to, from, next) => { if (to.name === 'OrderList') { const orders = await getOrders(); // 调用接口拿订单数据 to.meta.orders = orders; // 把数据存在路由meta里 } next(); }); // 组件内用 this.$route.meta.orders 取数据
方法4:用路由元信息(meta)控制组件行为
路由配置里的 meta
字段,能标记页面“是否需要刷新后重新请求数据”“是否需要权限”等,组件内根据 $route.meta
决定逻辑。
例子:详情页刷新后重新请求数据
路由配置:
{ path: '/article/:id', name: 'ArticleDetail', component: ArticleDetail, meta: { needReload: true } // 标记需要刷新后重新请求 }
组件内在 created
钩子(刷新后组件会重新实例化,created
会执行)里判断:
export default { name: 'ArticleDetail', data() { return { article: {} }; }, created() { if (this.$route.meta.needReload) { this.fetchArticle(); } }, methods: { async fetchArticle() { const { id } = this.$route.params; const res = await this.$api.getArticle(id); this.article = res.data; } } };
不同场景下的具体实现案例
下面用3个高频场景,手把手教你写代码解决问题。
场景1:登录状态刷新后不丢失
需求:用户登录后,刷新页面仍保持登录态,能正常访问需权限的页面。
实现步骤:
- 登录成功时,把token存到
sessionStorage
和Vuex
; - 页面刷新后,Vuex在初始化时读取
sessionStorage
的token,恢复状态; - 用全局路由守卫拦截未登录的权限页面。
代码示例:
-
Vuex模块(
store/user.js
):const state = { token: '', userInfo: {} }; const mutations = { SET_TOKEN(state, token) { state.token = token; sessionStorage.setItem('token', token); }, SET_USER_INFO(state, info) { state.userInfo = info; sessionStorage.setItem('userInfo', JSON.stringify(info)); } }; const actions = { // 登录动作 async login({ commit }, { username, password }) { const res = await loginApi({ username, password }); // 调用登录接口 commit('SET_TOKEN', res.token); commit('SET_USER_INFO', res.userInfo); }, // 初始化token(刷新后执行) initToken({ commit }) { const token = sessionStorage.getItem('token'); const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}'); if (token) { commit('SET_TOKEN', token); commit('SET_USER_INFO', userInfo); } } }; export default { namespaced: true, state, mutations, actions };
-
main.js
初始化Vuex:import Vue from 'vue'; import App from './App.vue'; import router from './router'; import store from './store'; new Vue({ router, store, beforeCreate() { this.$store.dispatch('user/initToken'); // 刷新后初始化token }, render: h => h(App) }).$mount('#app');
-
路由守卫(
router.js
):import Vue from 'vue'; import Router from 'vue-router'; import Home from './views/Home.vue'; import Admin from './views/Admin.vue'; import Login from './views/Login.vue'; Vue.use(Router); const router = new Router({ mode: 'history', routes: [ { path: '/', name: 'home', component: Home }, { path: '/admin', name: 'admin', component: Admin, meta: { requiresAuth: true } // 需登录 }, { path: '/login', name: 'login', component: Login } ] }); router.beforeEach((to, from, next) => { const isLogin = !!store.state.user.token; if (to.meta.requiresAuth && !isLogin) { next('/login'); // 没登录且需要权限,跳登录 } else { next(); // 放行 } }); export default router;
场景2:页面数据刷新后自动重新加载
需求:文章详情页(/article/:id
)刷新后,自动重新请求文章数据,避免页面空白。
实现步骤:
- 路由配置标记该页面需要刷新后重新请求;
- 组件在
created
钩子(刷新后会执行)里发起请求。
代码示例:
-
路由配置(
router.js
):{ path: '/article/:id', name: 'ArticleDetail', component: ArticleDetail, meta: { needReloadData: true } // 标记需要重新请求数据 }
-
组件(
ArticleDetail.vue
):<template> <div class="article-detail"> <h1>{{ article.title }}</h1> <p>{{ article.content }}</p> </div> </template> <script> export default { name: 'ArticleDetail', data() { return { article: {} }; }, created() { if (this.$route.meta.needReloadData) { this.fetchArticle(); } }, methods: { async fetchArticle() { const { id } = this.$route.params; // 假设api模块封装了请求 const res = await this.$api.getArticleById(id); this.article = res.data; } } }; </script>
场景3:解决history模式下刷新404问题
需求:用history模式开发,刷新页面不出现404,所有路径都能正确返回前端页面。
实现原理:
服务端需要配置“所有未匹配到的路径,都返回index.html”,让前端路由接管页面渲染。
不同服务器的配置示例:
-
Nginx:
在nginx.conf
的server
块里加:location / { try_files $uri $uri/ /index.html; }
意思是:先尝试找真实存在的文件(
$uri
),再找目录($uri/
),都没有就返回index.html
。 -
Node.js(Express框架):
const express = require('express'); const app = express(); // 静态文件托管(比如dist目录) app.use(express.static(path.join(__dirname, 'dist'))); // 所有未匹配的路由,返回index.html app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'dist/index.html')); }); app.listen(3000, () => { console.log('Server running on port 3000'); });
-
Vue CLI开发环境:
开发时用history模式,Vue CLI已经做了处理,刷新不会404;但打包后部署到生产环境,必须配服务端!
刷新时的性能与体验优化
解决状态问题后,还要让用户刷新时“不懵”——别白屏半天没反应,这部分分享几个提升体验的小技巧:
技巧1:加骨架屏/加载动画
刷新时,数据请求可能需要时间,给用户一个“页面在加载”的反馈。
实现思路:
在App.vue
里加个全局加载状态,路由切换或刷新时显示骨架屏,数据加载完再隐藏。
示例(简化版):
<template> <div id="app"> <skeleton v-if="isLoading" /> <!-- 骨架屏组件 --> <router-view v-else /> </div> </template> <script> export default { data() { return { isLoading: false }; }, created() { this.isLoading = true; // 假设在路由守卫里控制加载状态 this.$router.beforeEach((to, from, next) => { this.isLoading = true; next(); }); this.$router.afterEach(() => { this.isLoading = false; }); } }; </script>
技巧2:缓存高频使用的静态数据
比如网站的导航栏配置、用户权限菜单这些不常变的数据,存在localStorage里,刷新后先读缓存,再异步更新,减少等待时间。
示例:
// 封装一个获取权限菜单的函数 async function getAuthMenu() { const cache = localStorage.getItem('authMenu'); if (cache) { return JSON.parse(cache); // 先读缓存 } const res = await getAuthMenuApi(); // 调接口 localStorage.setItem('authMenu', JSON.stringify(res.data)); return res.data; } // 组件内使用 export default { data() { return { menu: [] }; }, async created() { this.menu = await getAuthMenu(); } };
技巧3:用NProgress做加载进度条
NProgress是个轻量的进度条库,能在路由切换、页面刷新时显示加载进度,让用户感知“页面在动”。
实现步骤:
- 安装:
npm i nprogress
; - 在
router.js
里配置:import NProgress from 'nprogress'; import
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。