Vue3 集成 VueRouter

Vue Router 是 Vue.js 的官方路由管理器,用于构建单页面应用程序(SPA)。它与 Vue.js 核心深度集成,使得构建单页应用变得简单高效。

使用场景

  • 构建单页面应用程序(SPA)
  • 需要前端路由管理的项目
  • 需要实现页面导航、路由守卫和懒加载等功能
  • 需要基于路由的参数传递和状态管理

注意事项

  1. Vue Router 4.x 专为 Vue 3 设计,与 Vue 2 需要使用 Vue Router 3.x
  2. 路由配置应合理组织,避免过于复杂嵌套
  3. 注意路由守卫的执行顺序和时机
  4. 动态路由参数变化时组件不会重新创建,需要使用监听器处理
  5. 路由模式分为 hash 模式和 history 模式,后者需要服务器配置支持

基本用法

安装与配置

js 复制代码
npm install vue-router@4
yarn add vue-router@4
pnpm add vue-router@4

配置 router/index.ts

创建 src/router/index.ts 文件

在 src 下创建 router 目录,然后在 router 目录里新建 index.ts 文件:

scss 复制代码
 └── src/
     ├── router/
         ├── index.ts  // 路由配置文件
tsx 复制代码
// router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';

// 定义路由配置
const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/HomeView.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/AboutView.vue')
  },
  {
    path: '/user/:id',
    name: 'User',
    component: () => import('@/views/UserView.vue'),
    props: true // 将路由参数作为 props 传递给组件
  }
];

// 创建路由实例
const router = createRouter({
  history: createWebHistory(), // 使用 HTML5 History 模式
  routes
});

export default router;

方式2

js 复制代码
import { type RouteRecordRaw } from 'vue-router';
declare module 'vue-router' {
  interface _RouteRecordBase {
    hidden?: boolean | string | number;
  }
}
const routes: RouteRecordRaw[] = [
  {
    path: '/index',
    name: 'index',
    component: () => {
      return import('../components/Homepage.vue');
    },
    meta: {
      keepAlive: true
    },
    children: [
      // 添加子路由
      {
        path: 'article',
        name: 'article',
        component: () => {
          return import('../views/page/article/index.vue');
        }
      },
      {
        path: 'TestTool',
        name: 'TestTool',
        component: () => {
          return import('../views/page/testTool/TestTool.vue');
        },
        children: [
          // 添加子路由
          {
            path: 'TestToolConvert',
            name: 'TestToolConvert',
            component: () => {
              return import('@/views/page/testTool/TestToolConvert.vue');
            }
          }
        ]
      }
    ]
  },
  { path: '/', redirect: { name: 'home' } }
];
export default routes;

新建router/index.ts

csharp 复制代码
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { createRouter, createWebHistory, _RouteRecordBase } from 'vue-router'
import routes from './routes'
import NProgress from 'nprogress'

const router = createRouter({
  history: createWebHistory(), //历史模式会制造页面刷新
  routes
})
// 页面切换之前取消上一个路由中未完成的请求
router.beforeEach((_to: any, _from: any, next: () => void) => {
  NProgress.start()
  next()
})
router.afterEach(() => {
  // 进度条
  NProgress.done()
})
export default router

挂载路由配置

main.ts 文件中挂载路由配置

tsx 复制代码
import { createApp } from 'vue'
import App from './App.vue'

import router from './router/index'
// use
const app = createApp(App)
app.use(router)

在组件中使用路由

js 复制代码
<template>
  <div>
    <nav>
      <router-link to="/">首页</router-link>
      <router-link to="/about">关于</router-link>
      <router-link :to="{ name: 'User', params: { id: 123 }}">用户页面</router-link>
    </nav>
    
    <!-- 路由出口 -->
    <router-view />
  </div>
</template>

<script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router'

// 获取路由实例和当前路由
const router = useRouter()
const route = useRoute()

// 编程式导航
const goToAbout = () => {
  router.push('/about')
  // 或者使用命名路由
  // router.push({ name: 'About' })
}

const goToUser = (id: number) => {
  router.push({ name: 'User', params: { id } })
}

// 替换当前路由(不添加历史记录)
const replaceRoute = () => {
  router.replace('/about')
}

// 前进后退
const goBack = () => {
  router.go(-1) // 后退一步
}

const goForward = () => {
  router.go(1) // 前进一步
}
</script>

路由出口

js 复制代码
<template>
  <div id="app">
    <router-view v-slot="{ Component }">
      <keep-alive>
        <component :is="Component" v-if="$route.meta.keepAlive" />
      </keep-alive>
      <component :is="Component" v-if="!$route.meta.keepAlive" />
    </router-view>
  </div>
</template>

常用操作

路由参数和查询参数

js 复制代码
<template>
  <div>
    <h1>用户详情</h1>
    <p>用户ID: {{ userId }}</p>
    <p>用户名: {{ username }}</p>
  </div>
</template>

<script lang="ts" setup>
import { useRoute } from 'vue-router'
import { computed, watch } from 'vue'

const route = useRoute()

// 获取路由参数
const userId = computed(() => route.params.id as string)

// 获取查询参数
const username = computed(() => route.query.name as string || '未知用户')

// 监听路由参数变化
watch(
  () => route.params.id,
  (newId) => {
    console.log('用户ID变化:', newId)
    // 可以在这里重新获取用户数据
  }
)
</script>

嵌套路由

typescript 复制代码
// router/index.ts
const routes: Array<RouteRecordRaw> = [
  {
    path: '/user/:id',
    component: () => import('@/views/UserLayout.vue'),
    children: [
      {
        path: '',
        name: 'UserProfile',
        component: () => import('@/views/UserProfile.vue')
      },
      {
        path: 'posts',
        name: 'UserPosts',
        component: () => import('@/views/UserPosts.vue')
      },
      {
        path: 'settings',
        name: 'UserSettings',
        component: () => import('@/views/UserSettings.vue')
      }
    ]
  }
]
vue 复制代码
<!-- UserLayout.vue -->
<template>
  <div>
    <h1>用户页面</h1>
    <nav>
      <router-link :to="{ name: 'UserProfile', params: { id: $route.params.id }}">资料</router-link>
      <router-link :to="{ name: 'UserPosts', params: { id: $route.params.id }}">帖子</router-link>
      <router-link :to="{ name: 'UserSettings', params: { id: $route.params.id }}">设置</router-link>
    </nav>
    
    <!-- 嵌套路由出口 -->
    <router-view />
  </div>
</template>

路由守卫

typescript 复制代码
// router/index.ts
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'

// 全局前置守卫
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
  // 可以在这里进行权限检查
  const isAuthenticated = checkAuth() // 假设的认证检查函数
  
  if (to.meta.requiresAuth && !isAuthenticated) {
    next({ name: 'Login', query: { redirect: to.fullPath } })
  } else {
    next()
  }
})

// 全局后置钩子
router.afterEach((to, from) => {
  // 可以在这里进行页面跟踪等操作
  document.title = to.meta.title as string || '默认标题'
})

// 路由独享的守卫
const routes: Array<RouteRecordRaw> = [
  {
    path: '/admin',
    component: () => import('@/views/AdminView.vue'),
    beforeEnter: (to, from, next) => {
      // 检查管理员权限
      if (isAdmin()) {
        next()
      } else {
        next({ name: 'Home' })
      }
    }
  }
]
vue 复制代码
<!-- 组件内守卫 -->
<script lang="ts" setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 在组件卸载前调用
onBeforeRouteLeave((to, from, next) => {
  // 可以在这里询问用户是否确认离开
  const answer = window.confirm('确定要离开吗?未保存的更改将会丢失。')
  if (answer) {
    next()
  } else {
    next(false)
  }
})

// 在当前路由更新但该组件被复用时调用
onBeforeRouteUpdate((to, from, next) => {
  // 可以在这里获取新的数据
  fetchUserData(to.params.id as string)
  next()
})
</script>

配置404页面.

修改router/routes.ts

* 代表通配符,若放在任意路由前,会被先匹配,导致跳转到 404 页面,所以需将如下配置置于最后。

js 复制代码
const routes = [
  ...//添加(放在最后)
  {
    path: "/:pathMatch(.*)*",
    component: () => import("@/pages/notFound.vue"),
  },
  {
  path: '*',
  name: '404'
  component: () => import('./404.vue')  
  }
];

路由重定向

在嵌套路由中,当访问/home时想重定向到/home/user

修改router/routes.ts

js 复制代码
{
    path: '/home',
    component: () => import('@/pages/home.vue'),
    redirect: '/home/user', //新增
    children: [
      {
        path: '/home/user',
        component: () => import('@/pages/user.vue'),
      },
      {
        path: '/home/manage',
        component: () => import('@/pages/manage.vue'),
      },
    ],
  },

刷新当前路由.

js 复制代码
//+new Date()保证每次点击路由的query项都是不一样的,确保会重新刷新view
const routers = async (path: string) => {
  await router.push({
    path: path,
    query: {
      t: +new Date()
    }
  })
}

跳转新窗口.

ts 复制代码
/**
 * @description: 跳转新页面
 * @param {string} url
 * @return {*}
 */
function winUrl(url: string): any {
  window.open(url)
}

async function resolveId(path: string, id: number) {
  const { href } = resolve(path, id)
  await winUrl(href)
}

高级用法

路由懒加载

使用() => import()方式导入的组件,只会在第一次进入页面时才会加载对应路由的组件

js 复制代码
// 方式1
const UserDetails = () => import('./views/UserDetails.vue')
const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: UserDetails }],
})

// 方式2
// 使用动态导入实现懒加载
const routes: Array<RouteRecordRaw> = [
  {    path: '/admin',
    name: 'Admin',
    component: () => import(/* webpackChunkName: "admin" */ '@/views/Admin.vue')
  }
]

滚动行为

typescript 复制代码
// router/index.ts
const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    // 返回滚动位置
    if (savedPosition) {
      return savedPosition
    } else if (to.hash) {
      return {
        el: to.hash,
        behavior: 'smooth'
      }
    } else {
      return { top: 0, left: 0 }
    }
  }
})

动态路由

typescript 复制代码
// 添加路由
const newRoute: RouteRecordRaw = {
  path: '/new-route',
  component: () => import('@/views/NewView.vue')
}
router.addRoute(newRoute)

// 添加嵌套路由
router.addRoute('parent-route', {
  path: 'child-route',
  component: () => import('@/views/ChildView.vue')
})

// 删除路由
router.removeRoute('route-name')

路由模式

Hash 模式

typescript 复制代码
import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(), // 使用 URL hash
  routes
})

History 模式

typescript 复制代码
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(), // 使用 HTML5 History API
  routes
})

路由导航

useRoute/useRouter.

html 复制代码
<script setup>
  import { useRoute, useRouter } from 'vue-router'
  const route = useRoute()  // 路由信息
  console.log(route.query)
  const router = useRouter()// 路由跳转
  router.push('/newPage')
</script>

路由导航流程.

  1. 导航被触发
  2. 在失活的组件里调用 beforeRouteLeave 守卫
  3. 调用全局 beforeEach 前置守卫
  4. 重用的组件调用 beforeRouteUpdate 守卫(2.2+)
  5. 路由配置调用 beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件里调用 beforeRouteEnter 守卫
  8. 调用全局的 beforeResolve 守卫(2.5+)
  9. 导航被确认
  10. 调用全局的 afterEach
  11. 触发 DOM 更新
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入

编程式导航

组合式API

js 复制代码
<script setup lang="ts">
import { useRouter } from 'vue-router';

const router = useRouter();

const handleManage = () => {
  router.push('/home/manage');
};
</script>

路由传参.

query传参

js 复制代码
//页面传参
<script setup lang="ts">
import { useRouter } from 'vue-router';

const router = useRouter();

const handleManage = () => {
  router.push({
    path: '/home/manage',
    query: {
      plan: '123', // t: +new Date()
    },
  });
};
</script>

//页面接参
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();

console.log(route.query.plan); //query接参 
</script>

无参跳转.

js 复制代码
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })

带参跳转.

js 复制代码
/**
 * 传值跳转
 * @param path 路径
 * @param value 值
 */
const routerId = async (path: string, value: number | string) => {
  await router.push({
    path: path,
    query: {
      id: value,
      t: +new Date()
    }
  })
}

获取跳转过来的参数

tsx 复制代码
import { useRoute } from 'vue-router'
 const route = useRoute()
 const state = reactive({
      id: route.query.id,
 })

动态路由匹配

js 复制代码
//定义路由
{
    path: '/register/:plan', // 动态字段以冒号开始
    component: () => import('@/pages/register.vue'),
  },
  
//页面传参
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();

const handleManage = () => {
  router.push('/register/123');
};
</script>

//页面接参
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();

console.log(route.params.plan); //params接参
</script>

存储懒加载组件

js 复制代码
// 用对象字面量来存储懒加载组件的路径和对应的组件函数
const asyncComponents = {
  home: () => {
    return import('@/components/MyHome.vue')
  },
  article: () => {
    return import('@/views/page/article/Index.vue')
  },
  column: () => {
    return import('@/views/page/article/components/column/ArticleColumn.vue')
  }
}

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'homes',
    meta: {
      keepAlive: true
    },
    component: asyncComponents.home
  },

导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。

路由导航守卫

html 复制代码
<script setup>
  import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

  // 添加一个导航守卫,在当前组件将要离开时触发。
  onBeforeRouteLeave((to, from, next) => {
    next()
  })

  // 添加一个导航守卫,在当前组件更新时触发。
  // 在当前路由改变,但是该组件被复用时调用。
  onBeforeRouteUpdate((to, from, next) => {
    next()
  })
</script>

全局前置守卫.

在路由跳转前触发,可在执行 next 方法前做登录判断,未登陆用户跳转到登录页

js 复制代码
const router = new createRouter({})
//to: 即将要进入的目标 用一种标准化的方式
//from: 当前导航正要离开的路由 用一种标准化的方式
router.beforeEach((to, from, next) => {
    if (to.path === '/login') {
      //在登录页做清除操作,如清除token等
    }
  
    if (!localStorage.getItem('token') && to.path !== '/login') {
      // 未登陆且访问的不是登录页,重定向到登录页面
      return '/login';
    }
  ...
  // 必须执行 next 方法来触发路由跳转 
  next() 
  // 返回 false 以取消导航
  return false
})

// 含有异步操作的方法
router.beforeEach(async (to, from, next) => {
  const res = await fetch("****");
  // to: 跳转到哪个路由
  // from: 从哪个路由跳转过来
  // next: 跳转函数,可以跳转到具体的 url
});

死循环解决

ue Router warn The "next" callback was called more than once in one navigation guard

js 复制代码
router.beforeEach(async (to, from, next) => {
    const token = Cookies.get('token');
    if (to.path === '/login' || to.path === '/') {
        next();
    }
    else {
        if (token) {
            next();
        } else {
          console.log('pms out');
            next('/login');
        }
    }
})

全局解析守卫

与 beforeEach 类似,也是路由跳转前触发,区别是还需在所有组件内守卫和异步路由组件被解析之后,也就是在组件内 beforeRouteEnter 之后被调用。

js 复制代码
router.beforeResolve((to, from, next) => {
  ...
  // 必须执行 next 方法来触发路由跳转 
  next() 
})

全局后置钩子

和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身。它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。

js 复制代码
router.afterEach((to, from) => {
  // ...
})

路由独享守卫.

使用场景:部分页面不需要登录,部分页面需要登录才能访问

可在路由配置上直接定义 beforeEnter

js 复制代码
const auth = () => {
  if (!localStorage.getItem("token")) {
    // 未登陆,重定向到登录页面
    return "/login";
  }
};

const routes = [
  ...{
    path: "/home",
    component: () => import("@/pages/home.vue"),
    redirect: "/home/user",
    children: [
      {
        path: "/home/user",
        component: () => import("@/pages/user.vue"),
      },
      {
        path: "/home/manage",
        component: () => import("@/pages/manage.vue"),
        beforeEnter: auth, //路由独享守卫
      },
    ],
  },
];

组件内的守卫.

使用情景:预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消

组件内可直接定义如下路由导航守卫

vue 复制代码
<script setup lang="ts">
import { onBeforeRouteLeave } from 'vue-router';

// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('确定离开吗');
  // 取消导航并停留在同一页面上
  if (!answer) return false;
});
</script>

路由元信息

将自定义信息附加到路由上,例如页面标题,是否需要权限,是否开启页面缓存等

使用路由元信息+全局前置守卫实现部分页面不需要登录,部分页面需要登录才能访问

修改router/index.ts

js 复制代码
const routes = [
  ...{
    path: "/home",
    component: () => import("@/pages/home.vue"),
    redirect: "/home/user",
    children: [
      {
        path: "/home/user",
        component: () => import("@/pages/user.vue"),
      },
      {
        path: "/home/manage",
        component: () => import("@/pages/manage.vue"),
        meta: {
          title: "管理页", // 页面标题
          auth: true, //需要登录权限
        },
      },
    ],
  },
];

修改router/index.ts

js 复制代码
router.beforeEach((to, from) => {
  if (!localStorage.getItem("token") && to.meta.auth) {
    // 此路由需要授权,请检查是否已登录
    // 如果没有,则重定向到登录页面
    return {
      path: "/login",
      // 保存我们所在的位置,以便以后再来
      query: { redirect: to.fullPath },
    };
  }
});

router-link 组件默认为a标签,在vue router 3.x中,可通过tag属性更改标签名,event属性更改事件名

在vue router 4.x中,这两个属性已被删除,通过作用域插槽(子组件给父组件传值的插槽)实现自定义导航标签

示例:将导航标签改为div,且需双击触发

active-class

vue 复制代码
<router-link v-slot="{ href, navigate, isExactActive }" to="/home/user" custom>
  <div :class="{ active: isExactActive }" :href="href" @dblclick="navigate">跳转user</div>
</router-link>
vue 复制代码
<!-- 字符串 -->
<router-link to="home">Home</router-link>

<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>

<!-- 不写 v-bind 也可以,就像绑定别的属性一样 -->
<router-link :to="'home'">Home</router-link>

<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>

<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

<!-- 带查询参数,下面的结果为 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>

设置 replace 属性,点击时,会调用 router.replace() 而不是 router.push(),导航后不会留下 history 记录。

html 复制代码
<router-link :to="{ path: '/abc'}" replace></router-link>

设置 append 属性后,则在当前 (相对) 路径前添加其路径。例如,我们从 /a 导航到一个相对路径 b,如果没有配置 append,则路径为 /b,如果配了,则为 /a/b

html 复制代码
<router-link :to="{ path: 'relative/path'}" append></router-link>

exact-active-class

配置当链接被精确匹配的时候应该激活的 class。可以通过以下代码来替代。

html 复制代码
<p>
   <router-link v-bind:to = "{ path: '/route1'}" exact-active-class = "_active">Router Link 1</router-link>
   <router-link v-bind:to = "{ path: '/route2'}" tag = "span">Router Link 2</router-link>
</p>

router.go(n).

这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

js 复制代码
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)

子路由.

js 复制代码
<a-menu-item key="1" @click="Routers('/Admin-index/ArticleTable')">文章列表</a-menu-item>
<router-view></router-view>

路由配置

tsx 复制代码
{
  path: '/Admin-index',
  name: 'Admin-index',
  component: () => import('@/views/admin/index/index.vue'),
  children: [   // 添加子路由
    {
      path: 'ArticleTable',
      name: 'ArticleTable',
      component: () => import('@/views/admin/article/ArticleTable.vue'),
    },
  ]
},

路由守卫及页面权限控制

js 复制代码
import router, { asyncRoutes } from '@/router'
import store from '@/store'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
// import getPageTitle from '@/utils/get-page-title'

//  to:要去哪个页面
//  from:从哪里来
//  next:它是一个函数。
//     如果直接放行 next()
//  to:要去哪个页面
//  from:从哪里来
//  next:它是一个函数。
//     如果直接放行 next()
//     如果要跳到其它页 next(其它页)
const whiteList = ['/login', '/404']

router.beforeEach(async(to, from, next) => {
  document.title = 'hr人力项目--' + to.meta.title
  //   console.log(to, '跳转至', from)
  // document.title = getPageTitle(to.meta.title)
  NProgress.start() // 开启进度条
  const token = store.state.user.token
  if (token) {
    // 已经登陆
    if (to.path === '/login') {
    // 如果当前在登录页,那么跳转首页
      next('/') // next('/') 只要指定地址跳转,就不会经过router.afterEach(),因此需要手动关闭进度条
      NProgress.done() // 关闭进度条
    } else {
      if (!store.getters.userId) {
      // 即将进入登录页调用获取用户信息的函数 [需满足两个条件,1) 需拥有token   2)并未在登录页上  ]
        const menuList = await store.dispatch('user/getUserInfo')
        console.log(menuList, 'menuListdsadasdsdsa')
        console.log(asyncRoutes, 'asyncRoutes')
        const filterRoutes = asyncRoutes.filter(route => {
          const routeName = route.children[0]
          console.log(route.children[0], 'route.children[0].name')
          return menuList.includes(routeName)
        })
        filterRoutes.push({ path: '*', redirect: '/404', hidden: true })
        // console.log(filterRoutes, 'filterRoutesfilterRoutes')
        router.addRoutes(filterRoutes)
        store.commit('menus/setMenuList', filterRoutes)
        // next({ ...to, replace: true })
        next(to.path)
        // 重新加载页面
      } else {
        next()
      }
      // 如果没有在登录页,那么放行,
    }
  } else {
    // 没有登录
    if (whiteList.includes(to.path)) {
    // 如果此时在白名单页面上,那么放行
      next()
    } else {
      // 如果此时不在白名单页面上,那么跳转至登录页
      next('/login')
      NProgress.done() // 关闭进度条
    }
  }
})
// 页面跳转之后执行钩子函数afterEach()
router.afterEach(() => {
  NProgress.done() // 关闭进度条
})

常见问题与解决方案

处理重复导航错误

typescript 复制代码
// 封装一个安全的导航函数
const safePush = (path: string) => {
  if (route.path !== path) {
    router.push(path)
  }
}

// 或者在全局错误处理中捕获
router.onError((error) => {
  if (error.message.includes('Avoided redundant navigation')) {
    // 忽略重复导航错误
  } else {
    // 处理其他错误
    console.error('路由错误:', error)
  }
})

处理未知路由

typescript 复制代码
// 添加一个捕获所有路由的规则
const routes: Array<RouteRecordRaw> = [
  // ...其他路由
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/NotFound.vue')
  }
]

路由对象/属性类型报错

引入 _RouteRecordBase 定义 hidden

js 复制代码
import {
    createRouter,
    createWebHashHistory,
    RouteRecordRaw,
    _RouteRecordBase 
} from 'vue-router'

declare module 'vue-router'{
    interface _RouteRecordBase {
        hidden?: boolean | string | number
    }
}

const routes: Array<RouteRecordRaw> = [
 {
        path: '/',
        redirect: '/login',
    },
    {
        path: '/login',
        name:'login',
       	hidden: false,
        component: () => import('@/views/login.vue'), // 懒加载组件
    }
]

-----vue3 TypeError: parentComponent.ctx.deactivate is not a function

只要为component动态组件添加一个唯一属性key即可

ini 复制代码
<component :is="Component" :key="route.name" v-if="route.meta.isKeepAlive"></component>

router 文件中,使用 pinia 报错

Vue 3​ 中,无论 main.js​ 里的 app.use(pinia)​ 写在 app.use(router)​ 前面还是后面,vue-router​,总是先初始化,所以会出现 pinia​ 使用报错。所以我们在使用 pinia​ 时需要在 router.beforeEach 函数中进行仓库初始化。

js 复制代码
// router/index.ts
import { useMenuStore } from "@/store/menu";
// 写在这里会报错
const menuStore = useMenuStore();

router.beforeEach(async (to, from, next) => {
  // ***
});

// 正常获取
router.beforeEach(async (to, from, next) => {
  // 不报错
  const menuStore = useMenuStore();
  // ***
});
相关推荐
披萨心肠3 小时前
Vue单向数据流下双向绑定父子组件数据
vue.js
接着奏乐接着舞。3 小时前
3D地球可视化教程 - 第3篇:地球动画与相机控制
前端·vue.js·3d·threejs
正在走向自律3 小时前
RSA加密从原理到实践:Java后端与Vue前端全栈案例解析
java·前端·vue.js·密钥管理·rsa加密·密钥对·aes+rsa
LuckySusu3 小时前
【vue篇】Vue 数组响应式揭秘:如何让 push 也能更新视图?
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 性能优化神器:keep-alive 深度解析与实战指南
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 核心机制揭秘:为什么组件的 data 必须是函数?
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 响应式陷阱:动态添加对象属性为何不更新?如何破解?
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 异步更新之魂:$nextTick 原理与实战全解
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 条件渲染终极对决:v-if vs v-show 深度解析
前端·vue.js