likes
comments
collection
share

Vue3全家桶之——Router4 路由

作者站长头像
站长
· 阅读数 19

安装使用

先初始化一个本地项目

npm create vite@latest

Vite创建的Vue3项目内容是空的,没有routerpinia等其他插件,需要自己集成。

npm install vue-router@4

Vue3安装的路由版本为router4

Vue2安装的路由版本为router3

src目录下新建router文件,然后在文件夹下新建index.ts路由文件。

import { createRouter, createWebHistory } from 'vue-router'
import Home  from ('../Home.vue') 
import Login from ('../Login.vue')

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/login',
      name: 'login',
      component: Login
    }
  ]
})

export default router

main.ts中挂载到Vue实例

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'

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

路由跳转

使用 router-link 组件进行导航,通过to来指定跳转的链接,可以在不刷新页面的情况下更改URL,从而跳转到其他页面。

router-view将显示与 URL 对应的组件

<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>
<div id="app">
  <router-link to="/">Go to Home</router-link>
  <router-link to="/login">Go to Login</router-link>

  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
</div>

路由模式

有两种路由模式HashHistory模式,分别通过createWebHashHistorycreateWebHistory来设置,这也是常见的Vue面试题

Hash模式

URL示例:http://example.com/#/home

URL中使用#符号来表示路由地址,即哈希片段。#符号后面的内容被称为哈希路径(hash path),由Vue Router用来匹配路由。浏览器不会将哈希片段发送到服务器,因此哈希模式不会影响服务器请求。

原理是通过onhashchange()事件监听hash值变化,在页面hash值发生变化后,window就可以监听到事件改变,并按照规则加载相应的代码。hash值变化对应的URL都会被记录下来,这样就能实现浏览器历史页面前进后退。

Hash易于部署,无需额外配置服务器;对于不支持HTML5 History API的浏览器,有较好的兼容性。但URL中包含#符号,有时可能被认为不够美观。

History模式

URL示例:http://example.com/home

在URL中不使用#符号,而是直接使用普通的URL路径。这需要服务器配置以支持该模式,并确保在用户访问页面时,服务器返回正确的资源而不是404错误。

history原理是使用HTML5 history提供的pushStatereplaceState两个API,用于浏览器记录历史浏览栈,并且在修改URL时不会触发页面刷新和后台数据请求。

URL更加美观,没有#符号;适合用于实际生产环境中,通常需要与服务器配合,支持HTML5 History API。但是在生产环境中需要特殊配置服务器,以避免刷新页面时出现404错误。

Nginx解决404问题

修改nginx.conf配置文件,在location下新增

location / {
  try_files $uri $uri/ /index.html;
}

命名路由

除了 path 之外,还可以为任何路由提供 name。这有以下优点:

  • 没有硬编码的 URL
  • params 的自动编码/解码。
  • 防止你在 URL 中出现打字错误。
  • 绕过路径排序(如显示一个) 在使用router-link组件to属性传递一个对象
<router-link :to="{ name: 'user', params: { username: 'erina' }}"></router-link>

路由懒加载

将路由组件按需加载,而不是一次性将所有路由组件都加载到初始页面中,把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件。这样可以减少初始加载时的资源体积,提高应用程序的加载速度,并减轻初始负载,是Vue性能优化的一种

vue-router支持动态import ,所有被导入的模块,在加载时就被编译。


// import Home  from ('../Home.vue') 
// import Login from ('../Login.vue')

const Home = () => import('../Home.vue')
const Login = () => import('../Login.vue')

编程式导航

除了<router-link>标签来定义导航链接,还可以通过router实例上的方法来跳转。通过触发某个事件,执行route.push

<template>
  <div @click="gotoHome">User</div>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router'
const route = useRouter()

function gotoHome() {
  route.push('/user')
}
</script>

该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })

// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })

路由传参

和之前的使用一样,可以通过queryparmas来传递参数

query

在使用编程式导航时,传入一个query对象为参数

function gotoHome(id) {
  route.push({
    path: '/user',
    query: {
      userId: id
    }
  })
}

使用userRoute里的query来接收参数

少了个r, 和路由跳转useRouter不一样,userRoute主要用来获取路由信息。


import { useRoute } from 'vue-router'
const route = useRoute()

console.log('user id', route.query.userId)

params

在使用编程式导航时,传入一个params对象为参数,此时只能使用命名路由name

function gotoHome(id) {
  route.push({
    name: 'user',
    params: {
      userId: id
    }
  })
}

使用params接收参数


import { useRoute } from 'vue-router'
const route = useRoute()

console.log('user id', route.params.userId)

动态路由

可以在路由URL后面拼接数据,达到传递参数的目的。在定义路由的时候,就要在path后定义路径参数。

路径参数用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中

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


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import ('../Home.vue')
    },
    {
      path: '/user/:userId',
      name: 'user',
      component: () => import ('../User.vue')
    }
  ]
})

export default router

路由跳转和params方式一样

function gotoHome(id) {
  route.push({
    name: 'user',
    params: {
      userId: id
    }
  })
}

接收参数也是

import { useRoute } from 'vue-router'
const route = useRoute()

console.log('user id', route.params.userId)

query和params传参的区别

  • query传参数配置的是path,而params传参配置的是name,在params中配置path无效
  • query在路由上可以不设置参数,而parmas是必须要设置
  • query传递的参数会在URL地址栏上显示
  • params参数在刷新页面后消失,query传递的参数不会。
  • query方式通过route.query方法获取参数,parmas通过route.params方法获取

历史记录

replace

replace方法用于导航到一个新的路由,但是它会替换掉当前的历史记录,而不会生成新的历史记录。

在使用replace方法后,用户将无法通过浏览器的"后退"按钮返回到之前的页面,而是直接返回到上一个历史记录的前一步。

<router-link replace to="/login">Go to Home</router-link>
// 将push替换成replace
route.replace('/login')

前进后退

const next = () => {
  // 前进,数量不限于1
  router.go(1)
}

const prev = () => {
	// 后退
	router.back()
}

嵌套路由

使用嵌套路由可以在一个父路由下定义多个子路由,使用children数据来接收子路由

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


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import ('../Home.vue')
    },
    {
      path: '/user',
      name: 'user',
      component: () => import ('../User.vue'),
      children: [
        {
          path: '', // 空路径表示父路由的默认子路由
          component: () => import ('../User.vue'),
        },
        {
          path: 'profile',
          component: () => import ('../UserProfile.vue'),
        },
        {
          path: 'posts',
          component: () => import ('../UserPosts.vue'),
      },
      ]
    }
  ]
})

export default router

children 配置只是另一个路由数组,就像 routes 本身一样。因此,可以根据自己的需要,不断地嵌套视图。

在组件中,也是要加上<router-view></router-view>

命名视图

命名视图可以将多个组件同时渲染到同一个路由的不同位置的方法,命名视图适用于一些布局较复杂的场景,比如同时在页面的顶部和底部显示不同的内容。可以为每个位置创建一个命名视图,然后在路由配置中根据需要将组件渲染到相应的位置。

定义路由的时候,在components中定义多个组件

import { createRouter, createWebHistory } from 'vue-router';

import MainLayout from './components/MainLayout.vue';
import HeaderComponent from './components/HeaderComponent.vue';
import ContentComponent from './components/ContentComponent.vue';
import FooterComponent from './components/FooterComponent.vue';

const routes = [
  {
    path: '/',
    components: {
      default: MainLayout,
      header: HeaderComponent,
      content: ContentComponent,
      footer: FooterComponent,
    },
  },
  // 其他路由配置...
];

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

export default router;

在组件上使用<router-view>时增加name属性,对应路由中多个组件名

<template>
  <div>
    <!-- 使用命名视图渲染 Header、Content 和 Footer -->
    <router-view name="header"></router-view>
    <router-view name="content"></router-view>
    <router-view name="footer"></router-view>
  </div>
</template>

重定向

重定向redirect

重定向用于在用户访问某个路由时将其自动重定向到另一个路由。

import { createRouter, createWebHistory } from 'vue-router';

import HomeComponent from './components/HomeComponent.vue';
import AboutComponent from './components/AboutComponent.vue';
import ContactComponent from './components/ContactComponent.vue';

const routes = [
  {
    path: '/',
    component: HomeComponent,
  },
  {
    path: '/about',
    component: AboutComponent,
  },
  {
    path: '/contact',
    component: ContactComponent,
    // 设置重定向
    redirect: '/about', // 将访问 '/contact' 重定向到 '/about'
  },
  // 其他路由配置...
];

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

export default router;

路由守卫

路由守卫是vue-router提供的一种特性,允许开发者在路由切换的过程中添加一些自定义的逻辑。这些逻辑可以用于控制路由导航的行为,例如在用户访问特定路由前进行身份验证、权限检查、处理未保存的表单数据等。

主要分为三种类型路由守卫:全局守卫组件独享守卫组件内守卫

每个守卫方法接收以下参数:

  • to: 要进入的目标路由对象
  • from:当前导航正要离开的路由对象
  • next可选:一个函数,用于决定是否允许路由切换,调用该函数时可以传递一个参数指定要跳转的路由路径。它有三种调用方式。
    • next():通过当前路由进入到下一个路由
    • next():中断当前路由
    • next('/')或者next({path: '/'}):中断当前路由,然后跳转到一个新的路由。

可以返回的值如下:

  • false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像调用router.push()一样,你可以设置诸如 replace: true name: 'home' 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 from 一样。
    router.beforeEach(async (to, from) => {
       if (
         // 检查用户是否已登录
         !isAuthenticated &&
         // ❗️ 避免无限重定向
         to.name !== 'Login'
       ) {
         // 将用户重定向到登录页面
         return { name: 'Login' }
       }
     })
    

全局守卫

全局前置守卫beforeEach

使用router.beforeEach 注册一个全局前置守卫,该钩子在每次路由切换前调用,可以用于进行全局的身份验证或权限检查

beforeResolve

使用router.beforeResolve 注册一个全局解析守卫,该钩子在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用

afterEach

使用router.afterEach 注册一个全局后置守卫,该钩子在每次路由切换后调用,可以用于执行一些全局的清理任务或者数据统计等。

组件独享守卫 beforeEnter

组件独享守卫只会在特定路由配置中生效,影响到某个特定路由及其子路由。

使用router.beforeEnter 注册一个全局后置守卫,该钩子在进入路由时触发,用于对该路由进行独立的身份验证或其他检查

组件内守卫

组件内守卫在路由组件内直接定义路由导航守卫

beforeRouteEnter

该钩子在渲染该组件的对应路由被验证前调用,不能获取组件实例 this。

不能访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

解决办法

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

beforeRouteUpdate

该钩子在当前路由改变,但是该组件被复用时调用

beforeRouteLeave

该钩子在导航离开渲染该组件的对应路由时调用

在setup中使用

vue3组合式API来编写组见,可以通过onBeforeRouteUpdateonBeforeRouteLeave 分别添加 updateleave 守卫。

import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from, next) => {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
})

onBeforeRouteUpdate(async (to, from) => {

})

完整的路由导航解析流程

  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 的回调函数,创建好的组件实例会作为回调函数的参数传入。

路由元信息

路由元信息是在Vue Router中用于给路由添加额外信息的一种机制。它允许在路由配置中定义自定义的数据字段,用于存储一些与路由相关的元数据,例如页面标题、权限要求、面包屑等。

每个路由配置对象可以包含一个名为meta的字段,该字段是一个对象,用于存储路由的元信息。这些信息可以在导航守卫、路由组件或其他地方进行访问,从而允许你在应用程序中根据路由的元信息来做出不同的处理。

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    children: [
      {
        path: 'new',
        component: PostsNew,
        // 只有经过身份验证的用户才能创建帖子
        meta: { requiresAuth: true }
      },
      {
        path: ':id',
        component: PostsDetail
        // 任何人都可以阅读文章
        meta: { requiresAuth: false }
      }
    ]
  }
]

要访问路由的元信息,可以在导航守卫中使用to对象来获取。例如,在全局前置守卫beforeEach中可以这样访问:

router.beforeEach((to, from, next) => {
  // 获取目标路由的元信息
  const requiresAuth = to.meta.requiresAuth;

  // 在这里进行一些逻辑判断,例如根据requiresAuth决定是否需要登录验证

  // 继续路由导航
  next();
});

而在组件中,可以通过route.meta来访问

import { useRoute } from 'vue-router'
const route = useRoute()

console.log('user id', route.query.meta.requiresAuth)

添加TS扩展

可以通过扩展 RouteMeta 接口来输入 meta 字段:

// typings.d.ts or router.ts
import 'vue-router'

declare module 'vue-router' {
  interface RouteMeta {
    // 是可选的
    isAdmin?: boolean
    // 每个路由都必须声明
    requiresAuth: boolean
  }
}

动态路由

通常在后台项目中,一般都是用动态路由,也就是路由列表由后台返回,前端拿到数据后动态的添加路由。这样做的好处是可以做到权限控制,不同的角色权限访问不同的内容。

主要使用的方法是router.addRoute

添加路由

使用router.addRoute来注册一个路由

router.addRoute({path: '/login', component: Login})

删除路由

  • 通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:
    router.addRoute({ path: '/about', name: 'about', component: About })
    // 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的
    router.addRoute({ path: '/other', name: 'about', component: Other })
    
  • 通过调用 router.addRoute() 返回的回调:
    const removeRoute = router.addRoute(routeRecord)
    removeRoute() // 删除路由如果存在的话
    
  • 调用router.removeRoute按名称删除路由
    router.remove('login')
    

当路由删除时,所有的子路有都会被删除

查看路由

  • router.hasRoute('login'):检查路由是否存在。
  • router.getRoutes():获取一个包含所有路由记录的数组。

其他文章