Code前端首页关于Code前端联系我们

Vue Router 页面刷新后怎么处理状态?

terry 1天前 阅读数 23 #Vue

做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数据清空)还是存在。

常见的刷新后问题有哪些?

刷新后踩的“坑”,本质是前端状态丢失服务端资源匹配这两类问题,具体表现特典型:

  1. 登录状态“消失”
    比如用Vuex存用户token,刷新后Vuex被重置,token没了,导致页面跳转到登录页,哪怕你把token存在sessionStorage里,Vuex不主动读的话,组件里也拿不到登录态。

  2. 页面数据需要“重新要”
    列表页刚加载完数据,一刷新,数据全没了,得重新调接口,因为组件里的data在刷新后会被重置,之前请求到的数据没了。

  3. 路由参数/权限逻辑“失效”
    比如详情页靠 $route.params.id 拿数据,刷新后参数还在,但如果是权限页面(比如管理员页),刷新后没重新验证权限,可能让未授权用户进入。

  4. 直接跳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:登录状态刷新后不丢失

需求:用户登录后,刷新页面仍保持登录态,能正常访问需权限的页面。

实现步骤:

  1. 登录成功时,把token存到 sessionStorageVuex
  2. 页面刷新后,Vuex在初始化时读取 sessionStorage 的token,恢复状态;
  3. 用全局路由守卫拦截未登录的权限页面。

代码示例:

  • 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)刷新后,自动重新请求文章数据,避免页面空白。

实现步骤:

  1. 路由配置标记该页面需要刷新后重新请求;
  2. 组件在 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.confserver块里加:

    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是个轻量的进度条库,能在路由切换、页面刷新时显示加载进度,让用户感知“页面在动”。

实现步骤:

  1. 安装:npm i nprogress
  2. router.js里配置:
    import NProgress from 'nprogress';
    import

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门