vue-router
使用 vue-router
<RouterLink />链接到to属性指定的路由<RouterView />路由组件的容器useRoute()获取路由对象useRouter()获取路由器对象
@/router/index.ts
@/router/index.ts
import {
createRouter,
createWebHistory,
type RouteRecordRaw,
} from "vue-router";
import LoginView from "@/views/LoginView.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
// 同步导入的路由组件, 合并打包
component: LoginView,
},
{
path: "/register",
// 异步导入的路由组件, 分开打包
component: () => import("@/views/RegisterView.vue"),
},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
export default router;<RouterLink to="/where" /> 和 <a href="/where"></a> 的区别
<RouterLink />在 hash 模式和 history 模式下的行为相同<RouterLink />会阻止<a>标签点击事件的默认行为, 不会重新加载页面
路由模式
| 路由模式 | vue-router |
|---|---|
| history 模式 (html5 模式): 推荐 | createWebHistory() |
| hash 模式: 不利于 SEO | createWebHashHistory() |
| memory 模式: 适用于 node 环境和 SSR, url 不会改变 | createMemoryHistory() |
hash-mode
location.hash 是 url 中的 hash 值, 例 https://161043261.github.io/homepage/frontend/vue-router#hash-mode, location.hash = '#hash-mode', 改变 url 中的 hash 值时, 页面不会重新加载, 通常用于单页面内的导航, 不需要服务器配置, 不利于 SEO
hash 模式和 hashchange 事件
- Vue 路由的 hash 模式通过改变
location.hash的值, 会触发 hashchange 事件 - vue-router 监听 hashchange 事件, 实现无刷新的路由导航
addEventListener("hashchange", (ev) => console.log(ev));history-mode
html5 模式 (history 模式): url 中没有 #, 需要服务器配置 fallback 路由
popstate 事件
- 改变 url 中的 hash 值时, 页面一定不会重新加载
- 点击浏览器的前进/后退按钮改变 url 时, 会触发 popstate 事件
- 调用
history.forward(),history.back(),history.go(delta: number)改变 url 时, 也会触发 popstate 事件 - 调用
history.pushState(),history.replaceState()改变 url 时, 不会触发 popstate 事件, 页面一定不会重新加载
具名路由
路由组件可以有一个唯一的名字
@/router/index.ts
@/router/index.ts
import {
createRouter,
createWebHistory,
type RouteRecordRaw,
} from "vue-router";
import LoginView from "@/views/LoginView.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "login",
component: () => import("@/views/LoginView.vue"),
},
{
path: "/register",
name: "register",
component: () => import("@/views/RegisterView.vue"),
},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
export default router;编程式导航
router.push向 history 栈顶添加一条记录router.replace替换 history 栈顶的记录
const router = useRouter(); // 获取路由器对象
const routeJumpByUrl = (url: string) => {
// window.history.pushState();
router.push(url);
// router.push({ path: url, replace: false });
};
const routeJumpByName = (name: string) => {
// window.history.replaceState();
router.replace({ name, replace: true });
};
const routeJump2prev = (delta?: number) => {
// window.history.go(delta ?? -1);
router.go(delta ?? -1);
// window.history.back();
router.back();
};
const routeJump2next = (delta?: number) => {
// window.history.go(delta ?? 1);
router.go(delta ?? 1);
// window.history.forward();
router.forward();
};路由传参
- query: url 查询参数
- params: url 路径参数
- window.history.state
- 路由前置守卫
@/router/index.ts
@/router/index.ts
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "login",
component: () => import("@/views/LoginView.vue"),
},
{
path: "/register",
name: "register",
component: () => import("@/views/RegisterView.vue"),
},
{
path: "/register/:id/:name?/:age?", // url 路径参数
// :id 必传参数
// :name? :age? 可选参数
name: "registerWithId",
component: () => import("@/views/RegisterView.vue"),
},
];布尔模式
props 设置为 true 时, route.params url 路径参数将被设置为路由组件的 props
const routes: Array<RouteRecordRaw> = [
{
path: "/register/:id/:name?/:age?", // url 路径参数
name: "registerWithId",
component: () => import("@/views/RegisterView.vue"),
props: true,
},
];对象模式
props 是一个对象时, 将该对象设置为路由组件的 props
const routes: Array<RouteRecordRaw> = [
{
path: "/register",
name: "register",
component: () => import("@/views/RegisterView.vue"),
props: { foo: "bar" }
},
];函数模式
props 是一个函数时, 将该函数的返回值设置为路由组件的 props
const routes: Array<RouteRecordRaw> = [
{
path: "/register",
name: "register",
component: () => import("@/views/RegisterView.vue"),
props: (route: RouteLocationNormalizedGeneric) => ({ ...route.query }),
},
];<RouterView /> 插槽
<template>
<!-- <RouterView /> 等价于 -->
<RouterView v-slot="{ route, Component }">
<!-- Component 必须有唯一的根元素 -->
<component :is="Component" />
</RouterView>
</template>使用 <Transition /> 过渡组件和 <KeepAlive /> 缓存组件
<template>
<RouterView v-slot="{ route, Component }">
<Transition>
<KeepAlive>
<component :is="Component" />
</KeepAlive>
</Transition>
</RouterView>
</template>嵌套路由
@/router/index.ts
@/router/index.ts
const routes: Array<RouteRecordRaw> = [
{
path: "/",
redirect: "/home", // 路由重定向
},
{
path: "/home",
component: () => import("@/views/HomeView.vue"),
children: [
{
path: "",
name: "login",
component: () => import("@/views/LoginView.vue"),
},
{
path: "register",
// path: "register", 实际路由 "/home/register"
// path: "/register", 实际路由 "/register"
name: "register",
component: () => import("@/views/RegisterView.vue"),
},
],
},
];具名 <RouterView />, 路由别名, 路由重定向
@/router/index.ts
@/router/index.ts
const routes: Array<RouteRecordRaw> = [
{
path: "/views",
// 路由别名
// alias: '/',
alias: ["/", "/home"],
// 路由重定向
// redirect: '/views/ab',
// redirect: {
// path: '/views/ab',
// // name: 'ab',
// },
redirect: (to) => {
console.log("[redirect] to:", to);
return {
// path: '/views/ab',
name: "ab",
query: to.query, // 默认
};
},
children: [
{
path: "/views/ab", // path: 'ab'
name: "ab",
components: {
// name="default"
default: () => import("@/views/AView.vue"),
// name="pageB"
pageB: () => import("@/views/BView.vue"),
},
},
{
path: "bc", // path: '/views/bc'
name: "bc",
components: {
// name="pageB"
pageB: () => import("@/views/BView.vue"),
// name="pageC"
pageC: () => import("@/views/CView.vue"),
},
},
],
},
];路由守卫
- 前置守卫函数在 redirect 重定向后, 路由跳转前执行
- 后置守卫函数在路由跳转后执行
前置守卫
router.beforeEach((to, from, next) => void)
写法 1
写法 1
const whitelist: string[] = ["/register", "/login"];
router.beforeEach(
(
to, // (重定向后的) 目的路由
from, // 源路由
next, // 放行函数
) => {
console.log("[beforeGuard] from:", from);
console.log("[beforeGuard] to:", to);
if (whitelist.includes(to.path) || sessionStorage.getItem("token")) {
next(); // 放行
} else {
next("/login"); // 重定向到登录
}
},
);后置守卫
router.afterEach((to, from) => void)
Demo: Progress Bar
@/components/ProgressBar.vue
@/components/ProgressBar.vue
<script lang="ts" setup>
import { computed, ref } from "vue";
const progress = ref(0);
const barWidth = computed(() => progress.value + "%");
let requestId = 0;
const loadStart = () => {
progress.value = 0;
const cb = () => {
if (progress.value < 100) {
progress.value++;
requestId = requestAnimationFrame(cb);
} else {
progress.value = 0;
cancelAnimationFrame(requestId);
}
};
// https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
// 要求浏览器在下一次重绘前, 调用回调函数 cb
requestId = requestAnimationFrame(cb);
};
const loadEnd = () => {
progress.value = 100;
setTimeout(() => {
requestId = requestAnimationFrame(() => {
progress.value = 0;
});
}, 300);
};
defineExpose({ loadStart, loadEnd });
</script>
<template>
<div class="fixed top-0 h-[3px] w-dvw">
<div class="bar h-[inherit] w-0 bg-lime-100" />
</div>
</template>
<style lang="css" scoped>
.bar {
width: v-bind(barWidth);
}
</style>路由元信息, 路由过渡动画
@/router/index.ts
@/router/index.ts
import {
createRouter,
createWebHistory,
type RouteRecordRaw,
} from "vue-router";
declare module "vue-router" {
interface RouteMeta {
title: string;
transition?: string;
}
}
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
component: () => import("@/views/HomeView.vue"),
// 路由元信息
meta: {
title: "Homepage",
// 路由过渡动画
transition: "animate__bounceIn",
},
},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
router.beforeEach((to /** from, next */) => {
if (to.meta.title) {
document.title = to.meta.title;
}
});
export default router;滚动行为
仅点击浏览器的前进/后退按钮 (触发 popstate 事件) 时可用
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
// 滚动行为
scrollBehavior: (to, from, savedPosition) => {
// 滚动到原位置
if (savedPosition) {
return savedPosition;
}
// 滚动到锚点
if (to.hash) {
return { el: to.hash, behavior: "smooth" };
}
// 滚动到顶部
return { top: 0 };
},
});动态路由
router.addRoute()动态添加路由, 返回删除该路由的函数router.removeRoute()动态删除路由router.hasRoute()判断路由是否存在router.getRoutes()获取所有路由信息
示例: 根据后端的响应, 动态添加路由
vite.config.ts
vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig, type Plugin } from "vite";
import vue from "@vitejs/plugin-vue";
import url from "node:url";
const vitePluginServer = (): Plugin => {
return {
name: "vite-plugin-server",
configureServer(server) {
server.middlewares.use("/routes", (req, res) => {
res.setHeader("Content-Type", "application/json");
const queryParams = url.parse(
req.originalUrl!,
true /** parseQueryString */,
).query;
const { username } = queryParams;
let resData: {
routes: { path: string; name: string; component: string }[];
} = {
routes: [],
};
switch (username) {
case "admin":
resData = {
routes: [
{ path: "/admin", name: "admin", component: "AdminView" },
{ path: "/admin2", name: "admin2", component: "AdminView2" },
],
};
break;
default:
resData = {
routes: [
{ path: "/user", name: "user", component: "UserView" },
{ path: "/user2", name: "user2", component: "UserView2" },
],
};
break;
}
res.end(JSON.stringify(resData));
});
},
};
};
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), vitePluginServer()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});Last updated on