前言:参考Vue Router,本文档例子使用 vue3 语法。
一:介绍
1. 什么是 Vue Router
是 Vue.js 的官方路由。功能包括:1. 路由映射。2. 动态路由。3. 路由配置。4. 路由参数。5. 过渡效果。6. 导航控制。7. CSS 类链接。8. HTML5 模式。9. 滚动行为。10. URL 编码
2. 安装
对于一个现有使用 JS 包管理的项目,添加 Vue Router 依赖:
npm install vue-router@4
# or
yarn add vue-router@4
# or
pnpm add vue-router@4
通过 create-vue 脚手架创建一个基于 Vite 的新项目,加入 Vue Router 的选项:
npm create vue@latest
# or
yarn create vue
# or
pnpm create vue
二:基础
1. 入门
router-view 显示与 URL 对应的组件。可以放在任何地方,以适应布局。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/vue-router@4"></script>
<div id="app">
<h1>Hello App!</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link to="/">Go to Home</router-link>
<router-link to="/about">Go to About</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</body>
<script>
// 1. 定义路由组件.
const Home = { template: "<div>Home</div>" };
const About = { template: "<div>About</div>" };
// 2. 定义一些路由
// 每个路由映射一个组件。
const routes = [
{ path: "/", component: Home },
{ path: "/about", component: About },
];
// 3. 创建路由实例并传递 `routes` 配置
// 可以在这里输入更多的配置
const router = VueRouter.createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
history: VueRouter.createWebHashHistory(),
routes, // `routes: routes` 的缩写
});
// 5. 创建并挂载根实例
const app = Vue.createApp({});
//确保 _use_ 路由实例使
//整个应用支持路由。
app.use(router);
app.mount("#app");
</script>
</html>
在 Vue3 中通过调用 useRouter 和 useRoute 函数创建实例来访问。
<script setup lang="ts">
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
router.push("/"); // 跳转
route.params; // 参数
</script>
2. 动态路由匹配
多个匹配路由映射到同一组件,通过一个动态字段来实现,称之为路径参数,路径参数用冒号 : 表示。如下 /users/johnny 和 /users/jolyne 会映射到同一个路由。
const User = {
template: "<div>User</div>",
};
// 这些都会传递给 `createRouter`
const routes = [
// 动态字段以冒号开始
{ path: "/users/:id", component: User },
];
通过 this.$route.params 来访问 URL 上的参数:
const User = {
template: "<div>User {{ $route.params.id }}</div>",
};
路由可以设置多个参数,在 $route.params 上相对应:
const User = {
template: "<div>User</div>",
};
// 传递给 `createRouter`
const routes = [
// 动态字段以冒号开始
{ path: "/users/:username/posts/:postId", component: User },
];
// 匹配路由:/users/eduardo/posts/123
// $route.params:{ username: 'eduardo', postId: '123' }
2.1 响应路由参数的变化
同一路由的不同参数跳转,因为映射的是相同组件,所以复用组件显得更高效,但生命周期函数就不会被调用,比如从 /users/johnny 导航到 /users/jolyne。要对同一路由的参数做出响应,需要用 watch 监听:
// vue3
import { watchEffect } from "vue";
watchEffect(() => {
console.log(1, route.params, route.query);
});
2.2 捕获所有路由或 404 Not found 路由
想匹配任意路径,使用路径参数正则表达式,在路径参数后面的括号中加入正则表达式 :
const routes = [
// 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
{ path: "/:pathMatch(.*)*", name: "NotFound", component: NotFound },
// 将匹配以 `/user-` 开头的所有内容,并将其放在 `$route.params.afterUser` 下
{ path: "/user-:afterUser(.*)", component: UserGeneric },
];
3. 路由的匹配语法
大多数应用都会使用 /about 静态路由和 /users/:userId 动态路由,但是 Vue Router 提供了更多的方式!
3.1 在参数中自定义正则
两个路由 /:orderId 和 /:productName,会匹配完全相同的 URL,需要一种方法来区分他们,最简单的方法是添加一个静态部分来区分:
const routes = [
// 匹配 /o/3549
{ path: "/o/:orderId" },
// 匹配 /p/books
{ path: "/p/:productName" },
];
如果不想添加静态部分,也可以添加正则,orderId 总是一个数字,productName 可以是任何东西。以在括号中为参数指定正则,现在,/25 将匹配 /:orderId,其他情况匹配 /:productName。
const routes = [
// /:orderId -> 仅匹配数字
{ path: "/:orderId(\\d+)" },
// /:productName -> 匹配其他任何内容
{ path: "/:productName" },
];
TIP:确保转义反斜杠( \ ),就像对 \d (变成\\d)所做的那样,在 JS 中实际传递字符串中的反斜杠字符。
3.2 可重复的参数
匹配 /first/second/third 路由,应该用 \*(0 个或多个)和 +(1 个或多个)将参数标记为可重复:
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: "/:chapters+" },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: "/:chapters*" },
];
提供了参数数组,而不是字符串,并且在使用命名路由时也需要你传递一个数组:
// 给定 { path: '/:chapters*', name: 'chapters' },
router.resolve({ name: "chapters", params: { chapters: [] } }).href;
// 产生 /
router.resolve({ name: "chapters", params: { chapters: ["a", "b"] } }).href;
// 产生 /a/b
// 给定 { path: '/:chapters+', name: 'chapters' },
router.resolve({ name: "chapters", params: { chapters: [] } }).href;
// 抛出错误,因为 `chapters` 为空
可以与正则结合使用:
const routes = [
// 仅匹配数字
// 匹配 /1, /1/2, 等
{ path: "/:chapters(\\d+)+" },
// 匹配 /, /1, /1/2, 等
{ path: "/:chapters(\\d+)*" },
];
3.3 Sensitive 与 strict 路由配置
默认情况下,所有路由是不区分大小写和带尾部斜线的路由的。例如,路由 /users 将匹配 /users、/users/、/Users/。通过 strict 和 sensitive 选项来修改。
const router = createRouter({
history: createWebHistory(),
routes: [
// 将匹配 /users/posva 而非:
// - /users/posva/ 当 strict: true,尾部不能有斜线
// - /Users/posva 当 sensitive: true,路由区分大小写
{ path: "/users/:id", sensitive: true },
// 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/
{ path: "/users/:id?" },
],
strict: true, // applies to all routes
});
3.4 可选参数
通过使用 ? 修饰符(0 个或 1 个)将一个参数标为可选:
const routes = [
// 匹配 /users 和 /users/posva
{ path: "/users/:userId?" },
// 匹配 /users 和 /users/42
{ path: "/users/:userId(\\d+)?" },
];
4. 嵌套路由
顶层的 router-view 渲染顶层路由匹配的组件。一个被渲染的组件也可以包含嵌套的 <router-view>:
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`,
};
要将组件渲染到嵌套的 router-view 中,需要在路由中配置 children,以 / 开头的嵌套路径将被视为根路径。不必使用嵌套的 URL:
const routes = [
{
path: "/user/:id",
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: "profile",
component: UserProfile,
},
{
// 当 /posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: "/posts",
component: UserPosts,
},
],
},
];
如上配置,访问 /user/eduardo ,User 的 router-view 什么都不展示,因为没有匹配到嵌套路由。可以提供一个空的嵌套路径,来展示一些东西:
const routes = [
{
path: "/user/:id",
component: User,
children: [
// 当 /user/:id 匹配成功
// UserHome 将被渲染到 User 的 <router-view> 内部
{ path: "", component: UserHome },
// ...其他子路由
],
},
];
5. 编程式导航
除了 <router-link> 定义导航链接,还可以使用 router 的实例方法来导航。router.push 返回的是 Promise。
5.1 router.push 导航
使用 router.push 方法,会向 history 栈添加一个新的记录,所以,当点击浏览器后退按钮时,会回到之前的 URL。当点击 <router-link> 时,内部会调用这个方法,所以点击 <router-link :to="..."> 相当于调用 router.push(...) :
// 字符串路径
router.push("/users/eduardo");
// 带有路径的对象
router.push({ path: "/users/eduardo" });
// 如果路由没有命名username参数,会报错,path: /user/:username?
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" });
使用 path 跳转 params 参数会无效,query 参数适用 path 或 name 跳转:
const username = "eduardo";
router.push(`/user/${username}`); // -> /user/eduardo
router.push({ path: `/user/${username}` }); // -> /user/eduardo
router.push({ name: "user", params: { username } }); // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: "/user", params: { username } }); // -> /user
5.2 替换当前位置
不会向 history 添加新记录,直接取代了当前的路由。用法:
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ path: "/home", replace: true });
// 相当于
router.replace({ path: "/home" });
</script>
<template>
<router-link :to="..." replace>
</template>
5.3 横跨历史
router.go(n) 用一个整数作为参数,表示在历史堆栈中前进或后退多少步,类似于 window.history.go(n)。
// 向前移动一条记录,与 router.forward() 相同
router.go(1);
// 返回一条记录,与 router.back() 相同
router.go(-1);
// 前进 3 条记录
router.go(3);
// 如果没有那么多记录,静默失败
router.go(-100);
router.go(100);
6. 命名路由
使用 name 命名路由。优点:1. 没有硬编码的 URL。2. params 的自动编码/解码。3. 防止你在 url 中出现打字错误。4. 绕过路径排序(如显示一个)。
const routes = [
{
path: "/user/:username",
name: "user",
component: User,
},
];
命名路由,通过 params 传递路由参数,如下路由将导航到 /user/erina。
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const handleGo = () =>
router.push({ name: "user", params: { username: "erina" } });
</script>
<template>
<router-link :to="{ name: 'user', params: { username: 'erina' } }">
User
</router-link>
<div class="home" @click="handleGo">home</div>
</template>
7. 命名视图
设置多个同级<router-view>,通过 name 属性确定渲染组件,默认 default:
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
多个视图就需要多个组件。正确配置 components (带上 s):
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/",
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
});
8. 重定向和别名
8.1 重定向
通过配置 redirect,实现路由重定向,当有 redirect 属性时,可以省略 component ,因为没有被访问,如下从 /home 重定向到 /:
const routes = [{ path: "/home", redirect: "/" }];
也可以使用 name 重定向:
const routes = [{ path: "/home", redirect: { name: "homepage" } }];
使用一个方法,动态返回重定向目标:
const routes = [
{
// /search/screens -> /search?q=screens
path: "/search/:searchText",
redirect: (to) => {
// 方法接收目标路由作为参数
// return 重定向的字符串路径/路径对象
return { path: "/search", query: { q: to.params.searchText } };
},
},
{
path: "/search",
// ...
},
];
8.2 别名
访问别名和访问路由一个效果,使用 alias 声明别名,以 / 开头,并可以使用数组提供多个别名:
const routes = [
{
path: "/",
component: Homepage,
alias: "/home",
},
{
path: "/users",
component: UsersLayout,
children: [
// 为这 3 个 URL 呈现 UserList
// - /users
// - /users/list
// - /people
{ path: "", component: UserList, alias: ["/people", "list"] },
],
},
];
9. 路由组件传参
在组件中使用 $route 会与路由紧密耦合,限制了组件的灵活性,因为它只能用于特定的 URL。可以通过 props 配置来解除这种行为:
const User = {
template: "<div>User {{ $route.params.id }}</div>",
};
const routes = [{ path: "/user/:id", component: User }];
// 替换成
const User = {
// 请确保添加一个与路由参数完全相同的 prop 名
props: ["id"],
template: "<div>User {{ id }}</div>",
};
const routes = [{ path: "/user/:id", component: User, props: true }];
9.1 命名视图
对于有命名视图的路由,必须为每个命名视图定义 props 配置:
const routes = [
{
path: "/user/:id",
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false },
},
];
9.2 函数模式
创建一个返回 props 的函数。可以将参数转换为其他类型:
const routes = [
{
path: "/demo/:id?",
name: "demo",
component: Demo,
props: (route) => {
return { id: +route.params.id };
},
},
];
9.3 Via RouterView
<RouterView v-slot="{ Component }">
<component
:is="Component"
view-prop="value"
/>
</RouterView>
10. 不同的历史模式
在创建路由器实例时,history 配置可以选择不同的历史模式。
10.1 Hash 模式
hash 模式是用 createWebHashHistory() 创建的,会在 url 后添加哈希字符(#),对 SEO 不友好,不推荐:
import { createRouter, createWebHashHistory } from "vue-router";
const router = createRouter({
// /home#/
history: createWebHashHistory(),
routes: [
//...
],
});
10.2 Memory 模式
Memory 模式是用 createMemoryHistory() 创建的,适合 Node 环境和 SSR,不推荐:
import { createRouter, createMemoryHistory } from "vue-router";
const router = createRouter({
history: createMemoryHistory(),
routes: [
//...
],
});
10.3 HTML5 模式
用 createWebHistory() 创建 HTML5 模式,URL 会看起来很 "正常",推荐使用这个模式:
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
});
三. 进阶
1. 导航守卫
导航守卫通过跳转或取消的方式守卫导航。路由导航包括:全局,单个路由,组件级。
1.1 全局前置守卫
使用 router.beforeEach 注册全局前置守卫,导航跳转前触发,接受两个参数,to 即将进入的路由,from 即将离开的路由,返回 false 是取消当前跳转,返回一个路由是跳转到返回路由中:
const router = createRouter({ ... })
router.beforeEach(async (to, from) => {
if (
// 检查用户是否已登录
!isAuthenticated &&
// ❗️ 避免无限重定向
to.name !== "Login"
) {
// 将用户重定向到登录页面
return { name: "Login" };
}
});
1.2 全局解析守卫
使用 router.beforeResolve 注册全局解析守卫。解析守卫在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。是获取数据或执行任何其他操作(如用户无法进入页面时希望避免执行的操作)的理想位置。例如:确保用户可以访问自定义 meta 属性 requiresCamera 的路由:
router.beforeResolve(async (to) => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission();
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false;
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error;
}
}
}
});
1.3 全局后置钩子
使用 router.afterEach 注册全局后置钩子,后置钩子不会改变导航本身,可以用于分析、更改页面标题、声明页面等辅助功能以及许多其他事情:
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath);
});
1.4 在守卫内的全局注入
使用 inject() 方法在导航守卫内注入全局属性。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();
// ...
});
1.5 路由独享的守卫
在路由配置上定义 beforeEnter 守卫,在进入路由时触发,不会在 params、query、hash 改变时触发:
const routes = [
{
path: "/users/:id",
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false;
},
},
];
也可以将函数数组传给 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],
},
];
1.6 组件内的守卫
在路由组件内直接定义路由导航守卫,onBeforeRouteLeave 离开组件时调用,onBeforeRouteUpdate 路由更新时调用:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from "vue-router";
onBeforeRouteLeave((to, from) => {});
onBeforeRouteUpdate((to, from) => {});
1.7 完整的导航解析流程
导航被触发。
在失活的组件里调用 onBeforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 onBeforeRouteUpdate 守卫(2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
调用全局的 beforeResolve 守卫(2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
2. 路由元信息
使用 meta 定义路由元信息,可以存放任何信息,如过渡名,路由访问权限等,可以在导航守卫被访问到:
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true },
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false },
}
]
}
]
怎么访问 meta 字段呢?
router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: "/login",
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
};
}
});
2.1 TypeScript
可以继承来自 vue-router 中的 RouteMeta 来为 meta 字段添加类型:
// 添加到一个 `.d.ts` 文件中。将这个文件包含在项目的 `tsconfig.json` 中的 "file" 字段内。
import "vue-router";
// 为了确保这个文件被当作一个模块,添加至少一个 `export` 声明
export {};
declare module "vue-router" {
interface RouteMeta {
// 是可选的
isAdmin?: boolean;
// 每个路由都必须声明
requiresAuth: boolean;
}
}
3. 数据获取
获取数据可以导航后在组件生命周期获取,也可以在导航前,在路由守卫获取。
3.1 导航完成后获取数据
马上导航和渲染组件,在 created 钩子中获取数据。
<script setup lang="ts">
import { useRoute } from "vue-router";
import { watchEffect, reactive } from "vue";
const route = useRoute();
watchEffect(() => {
fetchData(route.params.id);
});
const post = reactive(null);
const fetchData = (id) => {
getPost(id, (err, post) => {
post = post;
});
};
</script>
<template>
<div class="post">
<div class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
4. 组合式 API
4.1 在 setup 中访问路由和当前路由
不能直接访问 this.$router 或 this.$route。需使用 useRouter 和 useRoute 函数,但在模板中可以访问 $router 和 $route:
<script setup lang="ts">
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
const handleGo = () => {
router.push({ name: "Home", query: { ...route.query } });
};
</script>
<template>
<div class="demo" @click="handleGo">{{ $route.params }}</div>
</template>
4.2 导航守卫
onBeforeRouteLeave,onBeforeRouteUpdate 离开和更新守卫:
<script setup lang="ts">
import {
useRouter,
onBeforeRouteLeave,
onBeforeRouteUpdate,
} from "vue-router";
const router = useRouter();
onBeforeRouteUpdate(() => {
console.log("更新路由");
});
onBeforeRouteLeave(() => {
console.log("离开路由");
});
const changeRoute = () => {
router.push("/demo/2");
};
</script>
<template>
<div class="demo" @click="changeRoute">改变路由</div>
</template>
4.3 useLink
利用 RouterLink 构建自己的 RouterLink 组件或生成自定义链接:
<script setup lang="ts">
import { RouterLink } from "vue-router";
import { computed } from "vue";
const props = defineProps({
// @ts-ignore
...RouterLink.props,
inactiveClass: String,
_target: {
type: String,
default: "_self",
},
});
const isExternalLink = computed(
() => typeof props.to === "string" && props.to.startsWith("http")
);
</script>
<template>
<a v-if="isExternalLink" :href="to" :target="_target"><slot /></a>
<router-link v-else v-bind="$props" :to="$props.to"><slot /></router-link>
</template>
5. RouterView 插槽
RotuerView 暴露插槽,用来渲染路由组件:
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
<!-- 等价于 -->
<router-view />
5.1 KeepAlive & Transition
KeepAlive 是保持路由组件活跃,而不是 RouterView 本身。所以需要将 KeepAlive 放置在插槽内:
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
同理 Transition 实现路由组件之间切换的过渡效果:
<router-view v-slot="{ Component }">
<transition>
<component :is="Component" />
</transition>
</router-view>
5.2 模板引用
可以将模板引用放置在路由组件上:
<router-view v-slot="{ Component }">
<component :is="Component" ref="mainContent" />
</router-view>
6 过渡动效
要实现路由组件切换需要使用 <RouterView> 插槽:
<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>
6.1 单个路由的过渡
使用元信息和动态 name 结合实现每个路由的不同过渡效果:
const routes = [
{
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>
6.2 复用视图进行过渡
复用路由组件会忽略过渡,可以添加 key 属性来强制过渡:
<router-view v-slot="{ Component, route }">
<transition name="fade">
<component :is="Component" :key="route.path" />
</transition>
</router-view>
7. 滚动行为
通过 scrollBehavior 实现滚动效果,接收 to 和 from 路由对象:
const router = createRouter({
history: createWebHashHistory(),
routes: [...],
scrollBehavior (to, from) {
// return 期望滚动到哪个的位置
return { top: 0 }
}
})
通过 el 传递 CSS 选择器或 DOM 元素。top 和 left 的偏移量相对于该元素。
const router = createRouter({
scrollBehavior(to, from) {
// 始终在元素 #main 上方滚动 10px
return {
// 也可以这么写
// el: document.getElementById('main'),
el: "#main",
// 在元素上 10 像素
top: 10,
};
},
});
模拟 “滚动到锚点” 的行为:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
};
}
},
});
如果浏览器支持滚动行为,设置 behavior 让它变得更流畅:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: "smooth",
};
}
},
});
7.1 延迟滚动
通过返回一个 Promise,实现延时滚动:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ left: 0, top: 0 });
}, 500);
});
},
});
8. 路由懒加载
当打包时,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') },
],
})
8.1 把组件按组分块
8.1.1 使用 webpack
把某个路由下的所有组件打包到同个异步块 (chunk) 中。使用命名 chunk,特殊的注释语法:
const UserDetails = () =>
import(/* webpackChunkName: "group-user" */ "./UserDetails.vue");
const UserDashboard = () =>
import(/* webpackChunkName: "group-user" */ "./UserDashboard.vue");
const UserProfileEdit = () =>
import(/* webpackChunkName: "group-user" */ "./UserProfileEdit.vue");
8.1.2 使用 Vite
使用 rollupOptions 下定义分块:
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
// https://rollupjs.org/guide/en/#outputmanualchunks
output: {
manualChunks: {
"group-user": [
"./src/UserDetails",
"./src/UserDashboard",
"./src/UserProfileEdit",
],
},
},
},
},
});
9. 扩展 RouterLink
自定义 RouterLink 实现导航菜单链接,处理外部链接:
<script setup lang="ts">
import { RouterLink } from "vue-router";
import { computed } from "vue";
const props = defineProps({
// @ts-ignore
...RouterLink.props,
_target: {
type: String,
default: "_self",
},
});
const isExternalLink = computed(
() => typeof props.to === "string" && props.to.startsWith("http")
);
</script>
<template>
<a v-if="isExternalLink" v-bind="$attrs" :href="to" :target="_target"
><slot
/></a>
<router-link v-else v-bind="$props" :to="$props.to"><slot /></router-link>
</template>
10. 导航故障
10.1 检测导航故障
如果导航被阻,可以通过 router.push 返回的 Promise 的解判断是否离开了当前位置:
const navigationResult = await router.push("/my-profile");
if (navigationResult) {
// 导航被阻止
} else {
// 导航成功 (包括重新导航的情况)
this.isMenuOpen = false;
}
10.2 全局导航故障
使用 router.afterEach() 检测全局导航故障:
router.afterEach((to, from, failure) => {
if (failure) {
sendToAnalytics(to, from, failure);
}
});
11. 动态路由
11.1 添加/移除 路由
使用 router.addRoute() 和 router.removeRoute() 实现动态路由的添加和删除。
router.addRoute({ path: "/about", component: About });
router.removeRoute("About"); // 使用 name 移除
11.2 在导航守卫中添加路由
通过返回新的位置来触发重定向:
router.beforeEach((to) => {
if (!hasNecessaryRoute(to)) {
router.addRoute(generateRoute(to));
// 触发重定向
return to.fullPath;
}
});
11.3 添加嵌套路由
将路由的 name 作为第一个参数传递给 router.addRoute():
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 }],
});
11.4 查看现有路由
Vue Router 提供了两个功能来查看现有的路由:
| router.hasRoute(routerName):检查路由是否存在。
| router.getRoutes():获取一个包含所有路由记录的数组。