深入了解Vue的路由

216 阅读4分钟

基础

创建路由实例

我们使用的版本是4版本以上的语法,我们在探讨路由的时候也采用4以及4以上的语法进行学习。

import { createMemoryHistory, createRouter } from 'vue-router'

import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'

const routes = [
  { path: '/', component: HomeView },
]

const router = createRouter({
  history: createMemoryHistory(),
  routes,
})

其中routes定义路由本身,目的是将路由映射到组件。

  • history: createWebHashHistory()使用哈希的模式,实际 URL 之前使用一个哈希字符 (#) 来内部传递。由于 URL 的这一部分永远不会发送到服务器,因此它不需要服务器级别的任何特殊处理。
  • history: createWebHistory()的URL 将看起来“正常”,但是这个模式下我们需要配合通配符进行相关的配置
  • history: createMemoryHistory(),需要你在调用 app.use(router) 后推送初始导航。非常适合 Node 环境和 SSR

进行路由注入

const app = createApp(App);

app.use(router).mount('#app');

或者

const app = createApp(App) 
app.use(router) 
app.mount('#app')

注意 对 use() 的调用需要在对 mount() 的调用之前进行。

访问路由和当前路由

我们使用Vue3,所以我们常用的是组合式的API,具体的写法如下:

<script setup>
import { computed } from 'vue' 
import { useRoute, useRouter } from 'vue-router'

const router = useRouter()  // 可以进行路由跳转
const route = useRoute()  // 可以查询路由相关信息
</script>

我们经常将路由器实例称为 router,这是 createRouter() 返回的对象。当前路由将被称为 route

带参数的动态路由匹配

我们可能有一个 User 组件,它应该为所有用户渲染,但使用不同的用户 ID,我们通过 动态路由

import User from './User.vue'
// these are passed to `createRouter` 
const routes = [  { path: '/users/:id', component: User } ]

一个 参数 由冒号 : 表示。当路由匹配时,其 参数 的值将作为 route.params 公开给每个组件。因此,我们可以通过将 User 的模板更新为以下内容来渲染当前用户 ID

image.png

路由匹配语法

参数使用自定义表达式

我们有一种场景,我们需要在路由上取两个值,但是这两个值要根据类型放到不同的变量中,这种时候我们有2种解决方案。

方案一: 使用不同的静态路由进行区分,代码如下:

const routes = [ 
    // matches /o/3549 
    { path: '/o/:orderId' },
    // matches /p/books 
    { path: '/p/:productName' },
 ]

方案二:使用自定义的表达式进行区分,代码如下:

const routes = [ 
// /:orderId -> matches only numbers
{ path: '/:orderId(\\d+)' },
// /:productName -> matches anything else
{ path: '/:productName' }, ]

可重复参数

你需要匹配具有多个部分的路由,例如 /first/second/third,你应该使用 *(0 个或多个)和 +(1 个或多个)将参数标记为可重复的,代码如下:

const routes = [ 
    // /:chapters -> matches /one, /one/two, /one/two/three, etc 
    { path: '/:chapters+' },
    // /:chapters -> matches /, /one, /one/two, /one/two/three, etc 
    { path: '/:chapters*' }, 
]

这些也可以与自定义正则表达式结合使用,方法是在右括号后添加它们

const routes = [ 
    // only match numbers // matches /1, /1/2, etc 
    { path: '/:chapters(\\d+)+' }, 
    // matches /, /1, /1/2, etc 
    { path: '/:chapters(\\d+)*' }, 
]

敏感和严格路由选项

const router = createRouter({ 
    history: createWebHistory(), 
    routes: [ 
   // will match /users/posva but not:
   // - /users/posva/ because of strict: true
   

可选参数

可以使用 ? 修饰符(0 个或 1 个)将参数标记为可选,例子如下:

const routes = [ 
    // will match /users and /users/posva 
    { path: '/users/:userId?' },
    // will match /users and /users/42 
    { path: '/users/:userId(\\d+)?' }, 
]

路由命名

创建路由的时候可以给路由直接命名,如下面的配置

const routes = [
  {
    path: '/user/:username',
    name: 'profile', 
    component: User
  }
]

我们可以使用 name 而不是 path,将 to 属性传递给 <router-link>

使用 name 有多种优势

  • 没有硬编码的 URL。
  • 自动编码 params
  • 避免 URL 键入错误。
  • 绕过路径排名,例如显示与相同路径匹配的排名较低的路由。

嵌套路由

假设我们创建下面的路由来作为例子

<template>
    <router-view />
</template>
<template> 
    <div class="user"> 
        <h2>User {{ $route.params.id }}</h2> 
        <router-view /> 
    </div>
</template>

要将组件渲染到这个嵌套的 router-view 中,我们需要在任何路由中使用 children 选项。这个时候我们使用的配置需要修改到如下模式

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        // UserProfile will be rendered inside User's <router-view>
        // when /user/:id/profile is matched
        path: 'profile',
        component: UserProfile,
      },
      {
        // UserPosts will be rendered inside User's <router-view>
        // when /user/:id/posts is matched
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]

以 / 开头的嵌套路径将被视为根路径。

编程导航

要导航到不同的 URL,请使用 router.push。此方法将一个新条目推入历史堆栈,因此当用户单击浏览器后退按钮时,他们将被带回上一个 URL。

image.png

实际操作

定义基础路由表

我们需要先定义一份基础路由。这部分路由是不需要做权限限制的,我们任何时候都可以访问本路由,例如我们的登陆页面,404页面。

const routes =[
  {
    path: '/login',
    name: 'login',
    component: () => import(/* webpackChunkName: 'login' */ '@/views/login/index.vue'),
  },
    {
    path: '/NotFound',
    name: '404',
    component: () => import(/* webpackChunkName: 'login' */ '@/views/NotFound/index.vue'),
  },
]

获取动态路由

定义addRoute()方法,在登录后获取服务器路由通过这个方法添加路由,首先我们和后端获取到对路由定义的返回结构和字段,方便我们后续进行组装。

import Layout from '@/views/components/layout/index.vue';

const result =[
  {
    path: '/',
    name: 'Layout',
    component: Layout,
    children: [
        path: 'info', 
        name: 'userInfo', 
        component: 'user/info'
  }
]

添加路由

我们假设现在的登陆成功后,我们请求到了路由,然后我们将路由注入到我们现在从口中拿到的路由。

import { ref } from 'vue' 
import { useRouter } from 'vue-router'

const res_route = ref([])


const addRoute = async () => {
    // 当本地路由没有值的时候从后端获取路由
    if (!res_route.value.length) {
        // 从后段接口获取路由
        const res = await getRoutes()
        res_route.value = res
        // 把路由表的component字段转成真实的路由 
        server_route.value.map((_route) => {
            if (_route.component === 'Layout') { 
                break;
            }
            const children = _route.children
            // 根据字符串动态导入路由组件,这个时候表示数组中是有值的,不是一个空数组
            if (Array.isArray(children) && children?.length > 0) {
                children.map((childRoute) => {
                    childRoute.component = () => import(`@/views/${childRoute.path}.vue`)
                }
            }
        }
    }
    
}
server_route.value.map((route) => router.addRoute(route))

这个时候我们已经完成了路由的注入,但是我们的项目需要一些比较基础简单的功能,如:没有登录的用户需要重新跳转到登录页面,这个时候我们需要用到路由守卫功能,来满足我们的功能,结合上面的功能,我们将代码梳理一下。

router.beforeEach(async (to, from, next) => {
 const token = localStorage.getItem('token');
 if (token && token !== 'null') {
   if (!store.state.userInfo) {
     // 根据roles权限生成可访问的路由表
     server_route.value.map((route) => router.addRoute(route))
     let f_path = '';
     if (accessRoutes.length > 0) {
       const info = accessRoutes[0];
       f_path += info.path;
       if (info.children && info.children.length > 0) {
         f_path += '/' + info.children[0].path;
       }
     }
     const p_info = accessRoutes.find((item) => {
       return to.path.startsWith(item.path);
     });
     if ((to.path == '/' || !p_info) && f_path) {
       next({
         path: f_path,
         replace: true,
       }); // hack方法 确保addRoutes已完成
     } else {
       next({
         ...to,
         replace: true,
       }); // hack方法 确保addRoutes已完成
     }
   } else {
     next();
   }
 } else {
   if (to.path === '/login') {
     //如果是登录页面路径,就直接next()
     next();
   } else {
     //不然就跳转到登录;
     next('/login');
   }
 }
});