Vue Router【实用教程】(2024最新版)vue3 路由管理

Vue Router 是 Vue 官方的客户端路由解决方案,在单页应用 (SPA) 中,用户在应用中浏览不同页面时,URL 会随之更新,但页面不需要从服务器重新加载。

核心思想:

通过配置路由来告诉 Vue Router 为每个 URL 路径显示哪些组件。

官网

https://router.vuejs.org/zh/guide/

安装

通常创建 vue3 项目时,选择安装 Pinia 就会自动集成。

但若目前项目里没有,则按如下流程操作

npm install vue-router@4

新建文件 src/views/Index.vue

<template>
  <div>首页</div>
</template>

新建文件 src/router/index.ts

import { createRouter, createWebHistory } from "vue-router";
import Index from "../views/Index.vue";

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: "/",
      component: Index,
    },
  ],
});

export default router;

修改 src/App.vue 为

<template>
  <RouterView />
</template>

在 src/main.ts 中导入注册 Vue Router

import router from './router'
app.use(router)

内置组件

RouterLink

用于替代 <a> 标签,创建页面跳转的链接,实现在不重新加载页面的情况下改变 URL。

<RouterLink to="/">首页</RouterLink>
  • to 属性的值为 Vue Router 路由配置中的目标 URL(类似<a> 标签的 href 属性 )

  • to 属性的值可以是对象,语法和下文中的 push 相同(本质即自动调用 router.push 来触发导航)

  • replace 属性实现下文中 replace 的效果

    <router-link :to="..." replace>
    
  • RouterLink 的 v-slot 中可以访问与下文 useLink 组合式函数相同的属性

自定义 RouterLink

例如导航菜单中的链接,处理外部链接,添加 inactive-class 等

<script setup>
import { computed } from 'vue'
import { RouterLink } from 'vue-router'

defineOptions({
  inheritAttrs: false,
})

const props = defineProps({
  // 如果使用 TypeScript,请添加 @ts-ignore
  ...RouterLink.props,
  inactiveClass: String,
})

const isExternalLink = computed(() => {
  return typeof props.to === 'string' && props.to.startsWith('http')
})
</script>

<template>
  <a v-if="isExternalLink" v-bind="$attrs" :href="to" target="_blank">
    <slot />
  </a>
  <router-link
    v-else
    v-bind="$props"
    custom
    v-slot="{ isActive, href, navigate }"
  >
    <a
      v-bind="$attrs"
      :href="href"
      @click="navigate"
      :class="isActive ? activeClass : inactiveClass"
    >
      <slot />
    </a>
  </router-link>
</template>

RouterView

用于渲染当前 URL 路径对应的路由组件,可以放在任何地方,不一定要在 App.vue 中。

  <main>
    <RouterView />
  </main>

命名视图

  • name 属性可对视图进行命名(默认值为 default),用于在一个页面,渲染多个组件。
<router-view class="view left-sidebar" name="LeftSidebar" />
<router-view class="view main-content" />
<router-view class="view right-sidebar" name="RightSidebar" />

路由配置时,需配置多个组件,使用 components 属性,值为对象(属性为视图名称,属性值为组件)

    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },

也可在嵌套路由中使用

{
  path: '/settings',
  // 你也可以在顶级路由就配置命名视图
  component: UserSettings,
  children: [{
    path: 'emails',
    component: UserEmailsSubscriptions
  }, {
    path: 'profile',
    components: {
      default: UserProfile,
      helper: UserProfilePreview
    }
  }]
}

插槽

<router-view v-slot="{ Component }">
  <component :is="Component" />
</router-view>

等价于不带插槽的 <router-view />

用途1:添加组件缓存和路由切换的过渡

<router-view v-slot="{ Component }">
  <transition>
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </transition>
</router-view>

用途2:添加模板引用

<router-view v-slot="{ Component }">
  <component :is="Component" ref="mainContent" />
</router-view>

避免将引用放在 <router-view> 上,被 RouterView 的实例填充

用途3:插槽传参【不推荐】

<RouterView v-slot="{ Component }">
  <component
    :is="Component"
    view-prop="value"
   />
</RouterView>

所有视图组件都会接收到 view-prop,即所有的视图组件都声明了一个 view-prop 的 prop,但这未必需要。

路由懒加载

即当路由被访问时才加载对应组件,可以提升性能。

以下写法为静态导入组件

src/router/index.ts 中

import Index from "../views/Index.vue";
    {
      path: "/",
      component: Index,
    },

需改为动态导入

采用箭头函数的写法,返回 import() 函数

    {
      path: "/",
      component: () => import("../views/Index.vue"),
    },

建议:所有的路由都使用动态导入

配置路由

可通过下方链接,测试路由匹配规则
https://paths.esm.dev/

默认匹配模式

  • 不区分大小写
  • 匹配带有或不带有尾部斜线的路由

/users 将匹配 /users、/users/、甚至 /Users/

路由模式 history

【推荐】HTML5 模式

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

const router = createRouter({
  history: createWebHistory(),
  routes: [
    //...
  ],
})

Hash 模式

会在实际 URL 之前添加哈希字符 #

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

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    //...
  ],
})

Memory 模式

用于Node 环境和 SSR,不会有历史记录(无法后退或前进)。

import { createRouter, createMemoryHistory } from 'vue-router'
const router = createRouter({
  history: createMemoryHistory(),
  routes: [
    //...
  ],
})

路由选项 strict sensitive

在 src/router/index.ts 中添加选项

// 严格模式:不匹配带有尾部斜线的路由
strict: true, 
// 区分大小写
sensitive: true,

静态路由

像首页这种,路径和组件一一对应的关系为静态路由

    {
      path: "/",
      component: () => import("../views/Index.vue"),
    },

动态路由 :

若不同路径,对应的同一个组件,则是动态路由

使用场景

路径参数不同,但渲染同一个组件

实战范例

访问路由 ‘/user/1’ 和 ‘/user/2’ ,都渲染 User 组件,其中的路径参数 12 为用户 ID

    {
      path: "/users/:id",
      component: () => import("../views/User.vue"),
    },

一个路由中可设置多个路径参数

path: "/users/:username/posts/:postId",

页面模板中,可通过 $route.params 获取到路径参数,效果如下

匹配模式匹配路径route.params
/users/:username/users/eduardo{ username: 'eduardo' }
/users/:username/posts/:postId/users/eduardo/posts/123{ username: 'eduardo', postId: '123' }

动态路由切换时,会复用相同的组件实例(提升性能),所以组件的生命周期钩子不会被调用,此时要想对路径参数的变化做出响应,需使用 watch

<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

watch(() => route.params.id, (newId, oldId) => {
  // 对路由变化做出响应...
})
</script>

或 路由守卫 beforeRouteUpdate

<script setup>
import { onBeforeRouteUpdate } from 'vue-router'

onBeforeRouteUpdate(async (to, from) => {
  // 对路由变化做出响应...
  userData.value = await fetchUser(to.params.id)
})
</script>

自定义正则的路径参数

可选的路径参数,添加 ?

  // 匹配 /users 和 /users/posva
  { path: '/users/:userId?' },

限数字的路径参数,用 (\\d+)

// orderId 只能为数字
{ path: '/:orderId(\\d+)' },

可重复的参数,用 *(0 个或多个)和 +(1 个或多个)

  // /:chapters ->  匹配 /one, /one/two, /one/two/three, 等
  { path: '/:chapters+' },
  // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
  { path: '/:chapters*' },

结合使用 – 可重复的数字参数

  // 匹配 /1, /1/2, 等
  { path: '/:chapters(\\d+)+' },

命名路由 name

可以给路由取个名字,但所有路由的命名都必须是唯一的(路由重名时,只会保留最后一条)

  {
    path: '/user/:username',
    name: 'profile', 
    component: User
  }

可用于路由跳转

<router-link :to="{ name: 'profile', params: { username: 'erina' } }">
  User profile
</router-link>

使用 name 还有以下优点:

  • 没有硬编码的 URL。
  • params 的自动编码/解码。
  • 防止你在 URL 中出现打字错误。
  • 绕过路径排序,例如展示一个匹配相同路径但排序较低的路由。

嵌套路由 children

页面中的局部区域需要随路由改变时,则需要嵌套路由

实战范例

/user/:id/info 路由显示用户的信息
/user/:id/works 路由显示用户的作品

首先,需将 User 组件局部改变的区域替换为 <router-view />

<!-- User.vue -->
<template>
  <div class="user">
    <h2>User {{ $route.params.id }}</h2>
    <!-- 局部改变的区域 -->
    <router-view />
  </div>
</template>

嵌套路由的配置如下:

  {
    path: '/user/:id',
    component: User,
    children: [
      // 当 /user/:id 匹配成功
      // UserHome 将被渲染到 User 的 <router-view> 内部
      { path: '', component: UserHome },
      {
        // 当 /user/:id/info 匹配成功
        // UserInfo 将被渲染到 User 的 <router-view> 内部
        path: 'info',
        component: UserInfo,
      },
      {
        // 当 /user/:id/works 匹配成功
        // UserWorks将被渲染到 User 的 <router-view> 内部
        path: 'works',
        component: UserWorks,
      },

组件,其中的路径参数 12 为用户 ID

路由重定向 redirect

指当用户访问 /home 时,URL 会被 / 替换,然后匹配成 /

// 重定向至 -- 目标路径
[{ path: '/home', redirect: '/' }]

// 重定向至 -- 命名路由
[{ path: '/home', redirect: { name: 'homepage' } }]

// 属性值可以是方法,以便获取路由参数
{
  // /search/screens -> /search?q=screens
  path: '/search/:searchText',
  redirect: to => {
    // 方法接收目标路由作为参数
    // return 重定向的字符串路径/路径对象
    return { path: '/search', query: { q: to.params.searchText } }
  },
},

// 相对重定向
{
  // 将总是把/users/123/posts重定向到/users/123/profile。
  path: '/users/:id/posts',
  redirect: to => {
    // 该函数接收目标路由作为参数
    // 相对位置不以`/`开头
    // 或 { path: 'profile'}
    return 'profile'
  },
},

路由别名 alias

将 / 别名为 /home,意味着当用户访问 /home 时,URL 仍然是 /home,但会被匹配为用户正在访问 /。

{ path: '/', component: Homepage, alias: '/home' }

alias 的值可为数组

  {
    path: '/users',
    component: UsersLayout,
    children: [
      // 为这 3 个 URL 呈现 UserList
      // - /users
      // - /users/list
      // - /people
      { path: '', component: UserList, alias: ['/people', 'list'] },
    ],
  },

若路由有参数,在任何绝对别名中需包含它们:

  {
    path: '/users/:id',
    component: UsersByIdLayout,
    children: [
      // 为这 3 个 URL 呈现 UserDetails
      // - /users/24
      // - /users/24/profile
      // - /24
      { path: 'profile', component: UserDetails, alias: ['/:id', ''] },
    ],
  },

路由元信息 meta

在路由上添加更多信息,如过渡名称、访问权限等

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

在路由守卫(详见下文)中使用

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

404 路由

写在路由配置的末尾,用于匹配所有路由,展示路由匹配失败的 404 页面。

{ path: '/:pathMatch(.*)*', name: '404', component: () => import("../views/404.vue"), },

路径参数变组件传参 prop

即通过 defineProps 获取路径参数,提升了组件的通用性

<!-- User.vue -->
<script setup>
defineProps({
  id: String
})
</script>

<template>
  <div>
    User {{ id }}
  </div>
</template>

路由配置需添加 props 选项

{ path: '/user/:id', component: User, props: true }

命名视图的路由,需为每个命名视图定义 props 配置

  {
    path: '/user/:id',
    components: { default: User, sidebar: Sidebar },
    props: { default: true, sidebar: false }
  }

props 的值可以是函数(需为无状态的)

//  “/search?q=vue” 将传递 {query: 'vue'} 作为 props 传给 SearchUser 组件。
  {
    path: '/search',
    component: SearchUser,
    props: route => ({ query: route.query.q })
  }

路由器对象 $router

模板中为 $router

JS 中为

import { useRouter} from 'vue-router'

const router = useRouter()
  • 检查路由是否存在 router.hasRoute()
  • 获取所有路由记录的数组 router.getRoutes()

添加路由

在项目运行过程中,添加未注册的路由

router.addRoute({ path: '/about',name:'about', component: About })

为避免名字冲突,可以在路由中使用 Symbol 作为名字。

添加嵌套路由

router.addRoute({
  name: 'admin',
  path: '/admin',
  component: Admin,
  children: [{ path: 'settings', component: AdminSettings }],
})

router.addRoute({
  name: 'admin',
  path: '/admin',
  component: Admin,
  children: [{ path: 'settings', component: AdminSettings }],
})

删除路由

在项目运行过程中,删除已注册的路由,所有的别名和子路由也会被同时删除

方法1 添加重名路由

如果添加与现有途径名称相同的途径,会先删除路由,再添加路由。

router.addRoute({ path: '/about', name: 'about', component: About })
// 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的
router.addRoute({ path: '/other', name: 'about', component: Other })

方法2 调用添加路由的回调

适合没有名称的路由

const removeRoute = router.addRoute(routeRecord)
removeRoute() // 删除路由如果存在的话

方法3 removeRoute

router.removeRoute(‘home’)
  • 参数为路由的名称

当前路由对象 $route

模板中为 $route

JS 中为

import { useRoute } from 'vue-router'

const route = useRoute()

获取当前路由中的参数

route.params

useLink

Vue Router 暴露的路由信息,可用于构建自己的 RouterLink 组件或生成自定义链接,详细用法如下:

<script setup>
import { RouterLink, useLink } from 'vue-router'
import { computed } from 'vue'

const props = defineProps({
  // 如果使用 TypeScript,请添加 @ts-ignore
  ...RouterLink.props,
  inactiveClass: String,
}const {
  // 解析出来的路由对象
  route,
  // 用在链接里的 href
  href,
  // 布尔类型的 ref 标识链接是否匹配当前路由
  isActive,
  // 布尔类型的 ref 标识链接是否严格匹配当前路由
  isExactActive,
  // 导航至该链接的函数
  navigate
} = useLink(props)

const isExternalLink = computed(
  () => typeof props.to === 'string' && props.to.startsWith('http')
)
</script>

路由跳转

  • 模板中跳转路由,通过内置组件 RouterLink
  • JS 中跳转路由,通过路由器实例的各种 API

导航是异步的,返回一个 Promise!

首先获取路由器实例

import { useRouter} from 'vue-router'

const router = useRouter()

push 新增跳转

  • 会向 history 栈添加一个新的记录,当用户点击浏览器后退按钮时,会回到之前的 URL。( 效果同 window.history.pushState )
  • RouterLink 中 to 属性语法与 push 相同
  • params 不能与 path 一起使用
  • 任何类型的 params 都会转为字符串
// 字符串路径
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' })
const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo

replace 替代跳转

  • 不会向 history 添加新记录( 效果同 window.history.replaceState ),其他与 push 相同
  • push 添加 replace 参数,效果与 replace 相同
router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })

go 越级跳转

参数为整数,用于在历史堆栈中前进或后退多少步( 效果同 window.history.go)

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

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

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

// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)

路由守卫

即在路由跳转/取消时自定义逻辑。

全局前置守卫 beforeEach

导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。

src/router/index.ts 中

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})

参数

  • to —— 即将要进入的目标
  • from —— 当前导航正要离开的路由

返回值

  • false —— 取消当前的导航。
  • 一个路由地址 —— 重定向到一个不同的地址
  • undefined / true —— 执行导航/调用下一个导航守卫
 router.beforeEach(async (to, from) => {
   if (
     // 检查用户是否已登录
     !isAuthenticated &&
     // ❗️ 避免无限重定向
     to.name !== 'Login'
   ) {
     // 将用户重定向到登录页面
     return { name: 'Login' }
   }
 })

全局解析守卫 beforeResolve

在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。

router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

全局后置钩子 afterEach

用于分析、更改页面标题、声明页面等辅助功能

router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})

第三个参数为导航结果,值为 true/false

router.afterEach((to, from, failure) => {
  if (!failure) sendToAnalytics(to.fullPath)
})

路由守卫内的全局注入

vue3.3

在 app.provide() 中提供的所有内容都可以在 router.beforeEach()、router.beforeResolve()、router.afterEach() 内获取到

// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')

// router.ts or main.ts
router.beforeEach((to, from) => {
  const global = inject('global') // 'hello injections'
  // a pinia store
  const userStore = useAuthStore()
  // ...
})

路由配置中定义路由独享守卫

  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },

beforeEnter 守卫只在进入路由时触发,不会在 params、query 或 hash 改变时触发。

beforeEnter 属性值可以是一个函数数组,便于不同的路由重用守卫

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],
  },
]

组件中定义路由独享守卫

<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

// 路由发生变化时触发(仅动态路由参数变化,不会触发)
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm(
    'Do you really want to leave? you have unsaved changes!'
  )
  // 取消导航并停留在同一页面上
  if (!answer) return false
})

const userData = ref()

// 路由参数变化时触发
onBeforeRouteUpdate(async (to, from) => {
  //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
  if (to.params.id !== from.params.id) {
    userData.value = await fetchUser(to.params.id)
  }
})
</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 的回调函数,创建好的组件实例会作为回调函数的参数传入。

路由过渡 transition

全局过渡

所有路由切换都添加过渡效果

<router-view v-slot="{ Component }">
  <transition name="fade">
    <component :is="Component" />
  </transition>
</router-view>

根据路由元信息添加过渡

  {
    path: '/custom-transition',
    component: PanelLeft,
    meta: { transition: 'slide-left' },
  },
  {
    path: '/other-transition',
    component: PanelRight,
    meta: { transition: 'slide-right' },
  },
<router-view v-slot="{ Component, route }">
  <!-- 使用任何自定义过渡和回退到 `fade` -->
  <transition :name="route.meta.transition || 'fade'">
    <component :is="Component" />
  </transition>
</router-view>

根据路由添加过渡

<!-- 使用动态过渡名称 -->
<router-view v-slot="{ Component, route }">
  <transition :name="route.meta.transition">
    <component :is="Component" />
  </transition>
</router-view>

根据路径的深度动态添加信息到 meta 字段

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'
})

强制过渡 key

Vue 可能会自动复用看起来相似的组件,从而忽略了任何过渡,可通过 key 强制过渡

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

路由滚动 scrollBehavior

用于自定义路由切换时页面如何滚动

  • 只在支持 history.pushState 的浏览器中可用
const router = createRouter({
  history: createWebHashHistory(),
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
  }
})

scrollBehavior 函数

  • 参数 to —— 新路由
  • 参数 from —— 原路由
  • 参数 savedPosition —— 仅 popstate 导航时才可用(由浏览器的后退/前进按钮触发)
  • 返回值 —— ScrollToOptions 位置对象,若返回 falsy 值,或者空对象,则不滚动。
  scrollBehavior(to, from, savedPosition) {
    // 始终滚动到顶部
    return { top: 0 }
  },

相对目标元素偏移

  scrollBehavior(to, from, savedPosition) {
    // 始终在元素 #main 上方滚动 10px
    return {
      // 也可以这么写
      // el: document.getElementById('main'),
      el: '#main',
      // 在元素上 10 像素
      top: 10,
    }
  },

若返回 savedPosition,则按下 后退/前进 按钮时,就会像浏览器的原生表现那样

  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { top: 0 }
    }
  },

滚动到锚点 to.hash

  scrollBehavior(to, from, savedPosition) {
    if (to.hash) {
      return {
        el: to.hash,
      }
    }
  },

流畅滚动 smooth

  scrollBehavior(to, from, savedPosition) {
    if (to.hash) {
      return {
        el: to.hash,
        behavior: 'smooth',
      }
    }
  }

延迟滚动

通过返回一个 Promise实现,比如希望等待过渡结束后再滚动

const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ left: 0, top: 0 })
      }, 500)
    })
  },
})

路由检测

以下情况会路由故障(留在原页面上):

  • 用户已经位于他们正在尝试导航到的页面
  • 一个导航守卫通过调用 return false 中断了这次导航
  • 当前的导航守卫还没有完成时,一个新的导航守卫会出现了
  • 一个导航守卫通过返回一个新的位置,重定向到其他地方 (例如,return ‘/login’)
  • 一个导航守卫抛出了一个 Error

NavigationFailureType 可获知故障类型

import { NavigationFailureType} from 'vue-router'
  • aborted:在导航守卫中返回 false 中断了本次导航。
  • cancelled: 在当前导航完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了 router.push。
  • duplicated:导航被阻止,因为我们已经在目标位置了。

当次路由故障检测

const navigationResult = await router.push('/my-profile')

if (navigationResult) {
  // 导航被阻止
} else {
  // 导航成功 (包括重新导航的情况)
  this.isMenuOpen = false
}

跳转路由前,提醒页面未保存

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?')
}

也可用 .then 的写法

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

failure 对象中的 to 和 from 都是规范化的路由地址。

全局路由故障检测

router.afterEach((to, from, failure) => {
  if (failure) {
    sendToAnalytics(to, from, failure)
  }
})

检测重定向

通过读取路由地址中的 redirectedFrom 属性实现

await router.push('/my-profile')
if (router.currentRoute.value.redirectedFrom) {
  // redirectedFrom 是解析出的路由地址,就像导航守卫中的 to和 from
}
  • 34
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

朝阳39

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值