学完 Vue3 记不牢?快来看这篇精炼Vue3笔记复习一下 [ Route 篇 ]

Vue3 精炼复习 - Router 篇


前端的路由(Frontend Routing)是指在单页应用程序(Single Page Application,SPA)中,通过管理URL路径来控制不同页面或视图的展示,而不需要刷新整个页面。前端路由的核心在于利用JavaScript和浏览器的历史API来实现页面间的导航和内容切换,从而提供更快、更流畅的用户体验。

简单路由

如果只需要一个简单的页面路由,而不想为此引入一整个路由库,可以通过动态组件的方式,监听浏览器 hashchange 事件 或使用 History API 来更新当前组件。

html 复制代码
<!-- 监听 hashchange -->
<script setup>
import { ref, computed } from 'vue'
import Home from './components/Home.vue'
import Posts from './components/Posts.vue'
import NotFound from './components/NotFound.vue'

const routes = {
  '/': Home,
  '/posts': Posts
}

const currentPath = ref(window.location.hash)

window.addEventListener('hashchange', () => {
  currentPath.value = window.location.hash
})

const currentView = computed(() => {
  return routes[currentPath.value.slice(1) || '/'] || NotFound
})
</script>

<template>
  <a href="#/">Home</a> |
  <a href="#/posts">Posts</a> |
  <a href="#/non-existent-path">Broken Link</a>
  <component :is="currentView" />
</template>

安装与配置

路由组件通常存放在pagesviews文件夹,一般组件通常存放在components文件夹。

  1. 启动终端,输入:npm install vue-router,若启用 Typescript 则使用 npm install @types/vue-router --save-dev

  2. 在 src 中创建文件夹 router ,在里面创建文件 index.ts

  3. index.ts 内,调用 createRouter() 函数创建路由器实例,并 export 。

    js 复制代码
    import {createRouter, createWebHistory} from 'vue-router'
    
    // 引入要呈现的组件
    import Home from '@/pages/Home.vue'
    import Posts from '@/pages/Posts.vue'
    import NotFound from '@/pages/NotFound.vue'
    
    // 创建路由器
    const router = createRouter({
        history:createWebHistory(), //路由器的工作模式
        routes:[    // 路由规则
            { path:'/home', component:Home },
            { path:'/posts', component:Posts },
            { path:'*', component:NotFound },
        ]
    })
    export default router
  4. main.ts 引入路由器( 一定要在 mount app 之前)

    js 复制代码
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    
    const app = createApp(App)
    
    app.use(router)
    app.mount('#app')
  5. App.vue 中使用路由

    1. <RouterLink> 设置跳转链接:不同于常规的 <a> 标签, 该标签能够在不重新加载页面的情况下改变 URL,处理 URL 的生成、编码和其他功能。
    2. <RouterView> 设置组件展示位置
    3. active-class 设置路由启动时候的样式
    4. $route:可以在组件模板中使用 $route 来访问当前的路由对象。
    html 复制代码
    <template>
      <div class="app">
        <h2 class="title">Vue路由测试</h2>
        <!-- 导航区 -->
        <div class="navigate">
          <RouterLink to="/home" active-class="active">首页</RouterLink> |
          <RouterLink to="/posts" active-class="active">请求</RouterLink> |
          <RouterLink to="/dsadsacdsa" active-class="active">404</RouterLink>
        </div>
        <!-- 展示区 -->
          <strong>当前路由路径:</strong> {{ $route.fullPath }}
        <div class="main-content">
          <RouterView></RouterView>
        </div>
      </div>
    </template>
    
    <script lang="ts" setup name="App">
      import {RouterLink,RouterView} from 'vue-router'  
    </script>

路由器工作模式

历史模式(History Mode)也称 HTML5 模式

使用浏览器的History API (如history.pushStatehistory.replaceState)来管理URL。URL看起来像常规的路径(如/about)。

优点URL更加美观,不带有#,更接近传统的网站URL

缺点 :后期项目上线,需要服务端配合处理路径问题,否则刷新会有404错误。要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。

js 复制代码
const router = createRouter({
	history:createWebHistory(),
})

哈希模式(Hash Mode)

使用URL中的哈希(#)来模拟不同的路径。浏览器在哈希变化时不会发送请求,而是通过JavaScript处理(如/index.html#/about)。

优点:兼容性更好,因为不需要服务器端处理路径。

缺点:URL带有#不太美观,且在SEO(Search Engine Optimization,搜索引擎优化)优化方面相对较差。

js 复制代码
const router = createRouter({
	history:createWebHashHistory(),
})

Memory 模式

Memory 模式不会假定自己处于浏览器环境,因此不会与 URL 交互也不会自动触发初始导航。这使得它非常适合 Node 环境和 SSR。

它是用 createMemoryHistory() 创建的,并且需要你在调用 app.use(router) 之后手动 push 到初始导航。

js 复制代码
import { createRouter, createMemoryHistory } from 'vue-router'
const router = createRouter({
  history: createMemoryHistory(),
})

虽然不推荐,你仍可以在浏览器应用程序中使用此模式,但请注意它不会有历史记录 ,这意味着你无法后退前进

命名路由

routes 中的某项路由配置中,可以通过 name 属性给路由规则命名:

js 复制代码
routes:[
  {
    name:'zhuye',
    path:'/home',
    component:Home
  },
]

之后可以使用该 name 进行跳转:

html 复制代码
<!--直接通过名字跳转(to的对象写法配合name属性) -->
<router-link :to="{name:'zhuye'}">跳转</router-link>

重定向

在配置中使用 redirect,将路由重定向。

  1. 具体编码:

    js 复制代码
    {
        path:'/',
        redirect:'/about'
    }
  2. 重定向的目标也可以是一个命名的路由:

    js 复制代码
    const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
  3. 重定向的目标也可以是一个方法,动态返回重定向目标:

    js 复制代码
    {
        path: '/search/:searchText',
        redirect: to => {
          // 方法接收目标路由作为参数
          // return 重定向的字符串路径/路径对象
          return { path: '/search', query: { q: to.params.searchText } }
        },
    },
  4. 相对重定向(不以 / 开头)

    js 复制代码
    const routes = [
      {
        path: '/users/:id/posts',
        redirect: 'profile' // 会重定向到 /users/:id/profile
      },
    ]

在写 redirect 的时候,可以省略 component 配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 childrenredirect 属性,它也应该有 component 属性。

检测重定向

(先读编程式导航再回来读此部分更好理解)

当发生重定向时(从 /a 重定向 /b),Vue Router 会在目标路由(/b)的 route 对象上添加一个 redirectedFrom 属性,表示从哪(/a)重定向过来。

redirectedFrom存储原来导航目标的路由对象。

因此,通过读取路由地址中的 redirectedFrom 属性,对其进行不同的检查:

js 复制代码
await router.push('/a') // 路由里配置了会重定向到 /b

console.log(router.currentRoute.value.redirectedFrom)
// 输出:{ path: '/a', ... }(即原来的导航目标)

if (router.currentRoute.value.redirectedFrom) {
  // 如果重定向属性存在,那就 ...
}

别名

使用配置项alias为路由配上别名。

与重定向不同,使用别名时,URL 不会变更。

例如:/ 的别名设置为 /home ,当用户访问 /home 时,URL 仍然是 /home,但会被匹配为用户正在访问 /

js 复制代码
{
  path: '/',
  alias: '/home', // 通过/home或/都能访问
  component: Home
}

通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。

使别名以 / 开头,以使嵌套路径中的路径成为绝对路径。

甚至可以将两者结合起来,用一个数组提供多个别名:

js 复制代码
const routes = [
  {
    path: '/users',
    component: UsersLayout,
    children: [
      // 为这 3 个 URL 呈现 UserList(进入到UsersLayout component后,UserList component将自动呈现)
      // - /users
      // - /users/list
      // - /people
      { path: '', component: UserList, alias: ['/people', 'list'] },
    ],
  },
]

如果你的路由有参数,请确保在任何绝对别名中包含它们,否则会变得很奇怪。

路由参数传入与访问

查询参数 query

查询参数是 URL 中 ? 后面的部分,采用 key=value 的形式,多个参数用 & 连接:http://example.com/path?name=John&age=30

query 访问

params 不同 query 参数不需要在路由配置中预先声明,通过 route.query 访问。

html 复制代码
<template>
    <!-- 可以通过 $route 在模板中访问 -->
    {{ $route.query.name }}
</template>

<script setup>
// 使用 useRoute 在 setup 中访问
const route = useRoute()
console.log(route.query)
// 例如,访问 http://example.com/path?name=John&age=30 ,则就是 {name:"John",age:30}。
</script>
query 传入
html 复制代码
<!-- 跳转并携带query参数(to的字符串写法) -->
<RouterLink :to="/news?id=123&titile=123&content=123">
    {{news.title}}
</RouterLink>

<!-- 跳转并携带query参数(to的对象写法) -->
<RouterLink :to="{
  path: '/news',
  query: {
     id: news.id,
     title: news.title,
     content: news.content,
    }
  }">
    {{ news.title }}
</RouterLink>

动态路径参数 params

在 Web 前端中,动态路由匹配用于让页面根据 URL 的不同参数变化加载相应内容。

params 路由配置

路径参数 params 用在配置时冒号 : 表示,需在路由配置规则中提前设置。

使用 path 时不能使用 params,若之后要使用路由跳转的to的对象写法,必须使用name配置项。

js 复制代码
const routes = [
  // 动态字段以冒号开始
  { name:"userid", path: '/user/:id', component: User }
]
params 访问

设置后,匹配到的 params 值将在每个组件中以 route.params 的形式暴露出来。(选项式 API 用this.$route.params

html 复制代码
<template>
    <!-- 可以通过 $route 在模板中访问 -->
    User {{ $route.params.id }}
</template>

<script setup>
// 使用 useRoute 在 setup 中访问
const route = useRoute()
console.log(route.params.id)
// 例如,访问 /users/johnny ,则 id 就是 johnny。
</script>
params 传入

使用 path 时不能使用 params。传递params参数时,若使用to的对象写法,则必须使用name配置项。

html 复制代码
<!-- 跳转并携带params参数(to的字符串写法) -->
<RouterLink :to="`/news/detail/001/新闻001/内容001`">
    {{news.title}}
</RouterLink>
				
<!-- 跳转并携带params参数(to的对象写法) -->
<RouterLink 
  :to="{
    name:'xiang', //用name跳转
    params:{
      id:news.id,
      title:news.title,
      content:news.title
    }
  }"
>
  {{news.title}}
</RouterLink>
同路由多参数

可以在同一个路由中设置有多个 路径参数 ,它们会映射到 $route.params 上的相应字段。

js 复制代码
// router/index.ts
const routes = [
  { path: '/users/:username/posts/:postId', component: User }
]
/users/eduardo/posts/123

// script setup 内 (假设访问 /users/eduardo/posts/123 )s
console.log(route.params) // 得到 { username: 'eduardo', postId: '123' }

props 配置

作用:让路由组件更方便的收到参数(可以将路由参数作为props传给组件)

  1. 布尔值写法 :将路由收到的所有params参数作为props传给路由组件(只能传params参数)

    js 复制代码
    { name:'detail',
      path:'/detail/:id/:title/:content',
      component:Detail,
      props:true
    }
  2. 函数写法:把返回的对象中每一组key-value作为props传给Detail组件。

    • 接收一个参数,设定为当前配置的路由对象。
    • 返回对象可以设定为 route.query 从而使 query参数 也能以 props形式 便捷地传给组件。
    js 复制代码
    { path:'/detail',
      component:Detail,
      props(route){
        return route.query
      }
    }
  3. 对象写法:自己设定对象,把对象中的每一组key-value作为props传给Detail组件。

    js 复制代码
    { path:'/detail',
      component:Detail,
      props:{a:1,b:2,c:3}, 
    }

响应参数变化监控

  1. 监听 $route 对象的变化 : Vue 提供了 watch 方法,可以监听 route.params 的变化。这样我们可以在参数变化时更新页面内容:

    js 复制代码
    watch(() => route.params.id, (newId, oldId) => {
      // 对路由变化做出响应...
    })
  2. 使用 beforeRouteUpdate 导航守卫: 这个守卫在路由切换时触发,可以让我们提前响应参数变化,比如在新页面加载前获取新数据:

    js 复制代码
    // router/index.ts
    import { onBeforeRouteUpdate } from 'vue-router'
    onBeforeRouteUpdate(async (to, from) => {
      // 对路由变化做出响应...
      userData.value = await fetchUser(to.params.id)
    })

嵌套路由

一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下,URL 的片段通常对应于特定的嵌套组件结构。

要将组件渲染到这个嵌套的 <router-view> 中,我们需要在路由中配置 children

js 复制代码
// router/index.ts
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        // 当 /user/:id/profile 匹配成功,UserProfile 将被渲染到 User 的 <router-view> 内部
        path: 'profile',
        component: UserProfile,
      },
      {
        // 当 /user/:id/posts 匹配成功,UserPosts 将被渲染到 User 的 <router-view> 内部
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]
html 复制代码
<!-- App.vue -->
<template>
  <router-view />
</template>


<!-- User.vue -->
<template>
  <div>
    User {{ $route.params.id }}
    <router-view />
  </div>
</template>

路由跳转与展示

<RouterLink><router-link>是 Vue Router 提供的导航组件,用于在应用内部进行路由跳转。不同于常规的 <a> 标签, 该标签能够在不重新加载页面的情况下改变 URL,处理 URL 的生成、编码和其他功能。

  1. 字符串路径用法(最简单形式)

    html 复制代码
    <RouterLink to="/home">首页</RouterLink>
  2. 对象形式用法(更灵活)

    html 复制代码
    <RouterLink :to="{ path: '/user/123' }">用户</RouterLink>

    对象状态下能接收的参数:

    1. path:目标路由的路径

    2. name:命名路由的名称

    3. params:动态路径参数对象

    4. query:查询参数对象

    5. replace:是否替换当前历史记录(不添加新记录),传入值类型为 Boolean

      html 复制代码
      <RouterLink :to="{ path: '/login', replace: true }">登录</RouterLink>
    6. append:是否在当前路径后追加路径,传入值类型为 Boolean

      html 复制代码
      <!-- 当前路径为 /user,则生成路径 /user/profile -->
      <RouterLink :to="{ path: 'profile', append: true }">资料</RouterLink>
    7. force:是否强制刷新组件(Vue Router 4.1+),传入值类型为 Boolean

      html 复制代码
      <RouterLink :to="{ path: '/login', force: true }">刷新</RouterLink>
    8. state:通过 History API 传递的状态对象(Vue Router 4+)

      html 复制代码
      <RouterLink :to="{ path: '/checkout', state: { from: 'home', discount: true }}">结账</RouterLink>

    注意:使用 path 时不能使用 params

<RouterLink> 的可用属性。

属性 类型 默认值 说明
to String/Object 必填 目标路由路径或路由描述对象
replace Boolean false 是否替换当前历史记录(不添加新记录)
append Boolean false 是否在当前路径后追加路径
active-class String 'router-link-active' 匹配时的激活类名
exact Boolean false 开关精确匹配
exact-active-class String 'router-link-exact-active' 精确匹配时的激活类名
event String/Array 'click' 触发导航的事件

RouterView

<RouterView> 设置组件展示位置,是路由出口组件,用于渲染匹配到的路由组件。

命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。

<router-view>中传入 name 属性,为视图命名。未设置名字则默认为 default

在路由配置 router/index.ts 中,确保正确使用 components 配置 (单个为不带s,传入组件。多个则带上 s,传入对象)。

html 复制代码
<!-- 使用路由的组件 -->
<router-view class="view left-sidebar" name="LeftSidebar" />
<router-view class="view main-content" />
<router-view class="view right-sidebar" name="RightSidebar" />
js 复制代码
// router/index.ts
const router = createRouter({
  // ...
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        LeftSidebar, // LeftSidebar: LeftSidebar 的缩写.
        RightSidebar, // 它们与 `<router-view>` 上的 `name` 属性匹配.
      },
    },
  ],
})

编程式导航

除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

在组件内部,可用 $router 属性访问路由。使用组合式 API,则可用 useRouter() 来访问路由器。

浏览器的历史记录有两种写入方式:分别为pushreplace

push

使用 push 方法,相当于使用<RouterLink>to 属性,可进行路由跳转。

js 复制代码
import {useRoute,useRouter} from 'vue-router'

const router = useRouter()

// 字符串路径
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' })

注意 :如果提供了 pathparams 会被忽略,上述例子中的 query 并不属于这种情况。

router.push 和所有其他导航方法都会返回一个 Promise,让我们可以等到导航完成后才知道是成功还是失败。

js 复制代码
await router.push('/my-profile');
检测导航是否成功跳转

如果导航被阻止,导致用户停留在同一个页面上,由 router.push 返回的 Promise 的解析值将是 Navigation Failure 。否则,它将是一个 falsy 值(通常是 undefined)。这样我们就可以区分我们导航是否离开了当前位置:

js 复制代码
const navigationResult = await router.push('/my-profile')
if (navigationResult) {/*导航被阻止*/}
else {/*导航成功 (包括重新导航的情况)*/}
replace

replace是替换当前记录,即覆盖当前页面的历史记录(不产生新的历史记录,覆盖后无法回退到之前被覆盖的页面)。

在开启<RouterLink>中加入 replace 属性,可开启replace模式(传入值是 Boolean)。

html 复制代码
<RouterLink replace="true">News</RouterLink>

编程式导航则使用 router.replace,使用方法与 push 一致。

js 复制代码
const router = useRouter()
router.replace("...") // 使用方法
横跨历史

该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步,类似于 window.history.go(n)

js 复制代码
// 向前移动一条记录,与 router.forward() 相同
router.go(1)

// 返回一条记录,与 router.back() 相同
router.go(-1)

// 前进 3 条记录
router.go(3)

// 如果没有那么多记录,静默失败
篡改历史

router.pushrouter.replacerouter.gowindow.history.pushStatewindow.history.replaceStatewindow.history.go 的翻版,它们确实模仿了 window.history 的 API。

生成 URL

router.resolve 是 Vue Router 提供的一个方法,用于生成 URL解析路由。它可以根据路由名称或路径以及参数生成相应的 URL,通常用于导航、重定向和动态链接生成。

假设我们有一个带有动态参数 :id 的路由:

js 复制代码
const router = createRouter({
  history: createWebHistory(),
  routes:{ path: '/user/:id', name: 'user' }
})

使用 router.resolve 可以根据这个路由生成一个 URL。例如:

js 复制代码
// 生成一个 /user/123 的 URL
const routeInfo = router.resolve({ name: 'user', params: { id: '123' } })
console.log(routeInfo.href) // 输出 /user/123

路由元信息

通过接收属性对象的meta属性可以实现将任意信息附加到路由上,并且它可以在路由地址和导航守卫上都被访问到。

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

一个路由匹配到的所有路由记录会暴露为 route 对象(还有在导航守卫中的路由对象)的route.matched 数组。我们需要遍历这个数组来检查路由记录中的 meta 字段,但是 Vue Router 还为你提供了一个 route.meta 方法,它是一个非递归合并所有 meta 字段(从父字段到子字段)的方法。

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

导航守卫

vue-router提供的导航守卫主要用来通过跳转或取消的方式守卫导航。

全局前置守卫

可以使用 router.beforeEach 注册一个全局前置守卫:

  • 接收参数:
    • to: 即将要进入的目标
    • from: 当前导航正要离开的路由
  • 可以返回的值如下:
    • false: 取消当前的导航。URL 地址会重置到 from 路由对应的地址。
    • 一个路由地址: 通过一个路由地址重定向到一个不同的地址,如同调用 router.push(),且可以传入同样的配置项。

当一个导航触发时,全局前置守卫按照创建顺序调用。

守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中

js 复制代码
router.beforeEach(async (to, from) => {
    if (
        // 检查用户是否已登录(检测 to 是否Login,避免无限重定向)
        !(await isAuthenticated()) && to.name !== 'Login'
    ) {
        return { name: 'Login' } // 将用户重定向到登录页面
    }
})

如果遇到了意料之外的情况,可能会抛出一个 Error。这会取消导航并且调用 router.onError() 注册过的回调。

如果什么都没有,undefined 或返回 true,则导航是有效的,并调用下一个导航守卫

可选的第三个参数 next

在之前的 Vue Router 版本中,还可以使用 第三个参数 next

这是一个常见的错误来源,开发者们经过讨论将其移除。然而,它仍然是被支持的。

若要使用 next确保 其在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。

这里有一个在用户未能验证身份时重定向到/login的错误用例:

js 复制代码
// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})

全局解析守卫

可以用 router.beforeResolve 注册一个全局守卫。

router.beforeEach 类似,在每次导航 时都会触发。不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。

举例,根据路由在元信息中的 requiresCamera 属性确保用户访问摄像头的权限:

js 复制代码
router.beforeResolve(async to => {
  if (to.meta.requiresCamera) { // 前往的路由需要访问摄像头
    try {
      await askForCameraPermission() // 查看有无摄像头的权限
    } catch (error) {
      if (error instanceof NotAllowedError) {
        return false // ... 处理错误,然后取消导航
      } else {
        throw error // 意料之外的错误,取消导航并把错误传给全局处理器
      }
    }
  }
})

router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。

全局后置钩子

可用afterEach注册全局后置钩子,但这些钩子不会接受 next 函数也不会改变导航本身。它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。

js 复制代码
router.afterEach((to, from, failure) => {
  if (!failure) sendToAnalytics(to.fullPath)
  sendToAnalytics(to.fullPath)
})

路由独享的守卫

在当个路由配置项内,可以直接定义 beforeEnter 守卫,让其变为独享的路由守卫。

beforeEnter 守卫 只在进入路由时触发,不会在 paramsqueryhash 改变时触发。当配合嵌套路由使用时,父路由和子路由都可以使用 beforeEnter。如果放在父级路由上,路由在具有相同父级的子路由之间移动时不会被触发。

js 复制代码
{ path: '/users/:id',
  component: UserDetails,
  beforeEnter: (to, from) => {
    return false
  },
},

也可以将一个函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用。

js 复制代码
function removeQueryParams(to) {
  if (Object.keys(to.query).length)
    return { path: to.path, query: {}, hash: to.hash }
}

function removeHash(to) {
  if (to.hash) return { path: to.path, query: to.query, hash: '' }
}

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    path: '/about',
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
]

组件内的守卫

可以在路由组件内直接定义路由导航守卫(传递给路由配置的)。

选项式API

html 复制代码
<script>
export default {
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被验证前调用
    // 在此时进行获取数据是不错的选择,在加载组件的同时异步获取数据,减少等待时间。
    // 守卫执行时,组件实例未被创建,所以在此提供通过 next 的回调函数访问组件实例的机制
    next(vm => {/*通过 `vm` 访问组件实例,调用实例的代码或属性*/})
  }
  beforeRouteUpdate(to, from) {},
  beforeRouteLeave(to, from) {},
  setup(){},
}
</script>

组合式API

html 复制代码
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate, onBeforeRouteEnter } from 'vue-router'
    
onBeforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,由于会渲染同样的组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 此时组件实例已经挂载好了
}

onBeforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
}
</script>

<script setup> 没有beforeRouteEnter配置项,若想使用可以混用选项式 API :

html 复制代码
<script>
export default {
  beforeRouteEnter(to, from, next) {}
}
</script>

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

导航故障

检测导航故障

之前在编程式导航中有初步介绍router.push的返回值 Navigation FailureNavigation Failure 是带有一些额外属性的 Error 实例,这些属性提供哪些导航被阻止了以及为什么被阻止的信息。

要检查导航结果的性质,请使用 isNavigationFailure 函数:

js 复制代码
import { NavigationFailureType, isNavigationFailure } from 'vue-router'

// 试图离开未保存的编辑文本界面
const failure = await router.push('/articles/2')

if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
  // 给用户显示一个小通知
  showToast('You have unsaved changes, discard and leave anyway?')
}

全局导航故障

可用 router.afterEach() 导航守卫检测全局导航故障:

ts 复制代码
router.afterEach((to, from, failure) => {
  if (failure) {
    sendToAnalytics(to, from, failure)
  }
})

导航故障的属性

所有的导航失败都会暴露 tofrom 属性,以反映失败导航的当前位置和目标位置:

js 复制代码
// 正在从 / 尝试访问 admin 页面
router.push('/admin').then(failure => {
  if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
    failure.to.path // '/admin'
    failure.from.path // '/'
  }
})

RouterView 插槽

RouterView 组件暴露了一个插槽,可以用来渲染路由组件。其插槽提供两个属性:

  1. Component:当前路由匹配的组件实例
  2. route:当前路由对象

以下代码等价于不带插槽的 <router-view />

html 复制代码
<router-view v-slot="{ Component, route }">
  <component :is="Component" />
</router-view>

当想获得其他功能时,插槽提供了额外的扩展性:

  1. 比如插入KeepAlive & Transition组件,获得动效和保持路由组件活跃。

    html 复制代码
    <router-view v-slot="{ Component }">
      <transition name="fade">
        <keep-alive>
          <component :is="Component" />
        </keep-alive>
      </transition>
    </router-view>
  2. 比如使用模板引用直接获得路由组件示例

    html 复制代码
    <router-view v-slot="{ Component }">
      <component :is="Component" ref="mainContent" />
    </router-view>

路由组件过渡动效

单个路由的过渡

如果希望每个路由组件拥有其独特的过渡动效,可以使用 meta 元信息和动态的 name 结合在一起,放在<transition> 上:

js 复制代码
{ path: '/custom-transition',
  component: PanelLeft,
  meta: { transition: 'slide-left' },
},
{ path: '/other-transition',
  component: PanelRight,
  meta: { transition: 'slide-right' },
},
html 复制代码
<router-view v-slot="{ Component, route }">
  <!-- 使用自定义过渡,无自定义使用 `fade` -->
  <transition :name="route.meta.transition || 'fade'">
    <component :is="Component" />
  </transition>
</router-view>

强制在复用的视图之间进行过渡

Vue 可能会自动复用看起来相似的组件,从而忽略了任何过渡。可以添加``key` 属性来强制过渡。

html 复制代码
<router-view v-slot="{ Component, route }">
  <transition name="fade">
    <component :is="Component" :key="route.path" />
  </transition>
</router-view>

基于路由的动态过渡

我们可以添加一个afterEach全局后置钩子,根据路径的深度动态添加信息到 meta 字段。

html 复制代码
<!-- 使用动态过渡名称 -->
<router-view v-slot="{ Component, route }">
  <transition :name="route.meta.transition">
    <component :is="Component" />
  </transition>
</router-view>
js 复制代码
router.afterEach((to, from) => {
  const toDepth = to.path.split('/').length
  const fromDepth = from.path.split('/').length
  to.meta.transition = toDepth < fromDepth ? 'slide-right' : 'slide-left'
})

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

Vue Router 支持动态导入

js 复制代码
// 将 import UserDetails from './views/UserDetails.vue' 替换成以下
const UserDetails = () => import('./views/UserDetails.vue')

const router = createRouter({
  routes: [
    { path: '/users/:id', component: UserDetails }
    // 或在路由定义里直接使用它
    { path: '/users/:id', component: () => import('./views/UserDetails.vue') },
  ],
})

componentcomponents 的配置也可以接收一个返回 Promise 组件的函数:

js 复制代码
const UserDetails = () =>
  Promise.resolve({
    /* 组件定义 */
  })

动态路由

对路由的添加通常是通过 routes 选项来完成的,但是在某些情况下,开发者可能想在应用程序已经运行的时候添加或删除路由。

  • router.hasRoute(name):检查以该名称命名的路由是否存在,返回一个 Boolean。

  • router.getRoutes():获取一个包含所有路由记录的数组。

  • router.addRoute():添加一个路由,其会返回一个回调以方便删除操作。

    js 复制代码
    const removeRoute = router.addRoute({ path: '/about', component: About })
    removeRoute() // 删除路由如果存在的话
    • 若添加一个名称冲突的路由,会先删除原有路由,再添加新路由。

    • 若第一个参数放入的是已存在路由的名称,则可添加为嵌套路由。

      js 复制代码
      router.addRoute({ name: 'admin', path: '/admin', component: Admin })
      router.addRoute('admin', { path: 'settings', component: AdminSettings })
      // 等效于
      router.addRoute({
        name: 'admin',
        path: '/admin',
        component: Admin,
        children: [{ path: 'settings', component: AdminSettings }],
      })
  • router.removeRoute() 按名称删除路由

    js 复制代码
    router.removeRoute('about')
相关推荐
lb291710 分钟前
CSS 3D变换,transform:translateZ()
前端·css·3d
啊阿狸不会拉杆13 分钟前
第二十二章:Python-NLTK库:自然语言处理
前端·python·自然语言处理
萧寂17314 分钟前
html实现手势密码
前端·css·html
excel17 分钟前
webpack 核心编译器 八 节
前端
JoyZ20 分钟前
AI的时代学习还有意义吗?
前端
好_快22 分钟前
Lodash源码阅读-getSymbolsIn
前端·javascript·源码阅读
好_快22 分钟前
Lodash源码阅读-baseAssignValue
前端·javascript·源码阅读
kiss strong23 分钟前
Spring Boot向Vue发送消息通过WebSocket实现通信
vue.js·spring boot·websocket
好_快23 分钟前
Lodash源码阅读-copyObject
前端·javascript·源码阅读