Skip to Content
DocsFrontendvue-router

vue-router

使用 vue-router

  • <RouterLink /> 链接到 to 属性指定的路由
  • <RouterView /> 路由组件的容器
  • useRoute() 获取路由对象
  • useRouter() 获取路由器对象
@/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> 的区别

  1. <RouterLink /> 在 hash 模式和 history 模式下的行为相同
  2. <RouterLink /> 会阻止 <a> 标签点击事件的默认行为, 不会重新加载页面

路由模式

路由模式vue-router
history 模式 (html5 模式): 推荐createWebHistory()
hash 模式: 不利于 SEOcreateWebHashHistory()
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
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(); };

路由传参

  1. query: url 查询参数
  2. params: url 路径参数
  3. window.history.state
  4. 路由前置守卫
@/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
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
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
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
<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
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
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