Skip to content

Vue-Router 笔记

目录

入门 | Vue Router (vuejs.org)

快速部署(TS)

TS
// @/router/index.ts
// 导入页面
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

// 配置路由
const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: [
        {
            path: '/',
            name: 'home',
            alias: '/home',
            component: HomeView
        },
        {
            // 页面路径
            path: "/square",
            // 页面名
            name: "square",
            // 对于的vue代码,这样写页面会在打开时才加载
            component: () => import('@/views/square.vue')
        },
        {
            path: "/knowledgebase/:id*",
            name: "knowledgebase",
            component: () => import('@/views/KnowledgeBaseView.vue')
        },
        {
            path: "/content/:id",
            name: "new",
            component: () => import('@/views/ContentView.vue')
        },
        {
            // 匹配其他漏掉的页面
            path: "/:pathMatch(.*)*",
            name: "404",
            component: () => import('@/views/404View.vue')
        }

    ]
})

// 路由守卫
router.afterEach((to, from) => {
    const hash = to.hash
    if (hash) {
        const targetElement = document.getElementById(hash.substring(1));
        if (targetElement) {
            targetElement.scrollIntoView();
        }
    }
})

// 导出路由
export default router

使用

ts
// @/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");
vue
// @/App.vue
<template>
  <!-- 使用路由 -->
  <router-view></router-view>
</template>

// xxx.vue
<template>
  <!-- 跳转到对应组件 -->
  <router-link to="/home">home</router-link>
</template>

配置 Vue-Router

import components | 导入路由组件

2 种导入方法

TS
// 首页加载时就会加载页面(不管首页是不是它)
import HomeView from '../views/HomeView.vue'

// 只有访问了对应的路径才会加载页面,可以加快首页的加载速度
{
    path: '/',
    name: 'home',
    alias: '/home',
    component: () => import('@/views/HomeView.vue')
}

// 补充一种奇怪的东西
// 路由组件对象只需要有component属性就行(怪)
const User = {
  template: '<div>User</div>',
}
const routes = [
  { path: '/users', component: User },
]

createRouter | 路由配置

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

history | 不同的历史模式

Hash 模式

hash 模式是用 createWebHashHistory() 创建的:

js
import { createRouter, createWebHashHistory } from "vue-router";

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

它在内部传递的实际 URL 之前使用了一个哈希字符(#)。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,它在 SEO(搜索引擎优化) 中确实有不好的影响。如果你担心这个问题,可以使用 HTML5 模式。

并且使用了这个之后就不能用页面内跳转和跳转到页面指定部分了,因为页面内跳转就是基于这个的

HTML5 模式

createWebHistory() 创建 HTML5 模式,推荐使用这个模式:

js
import { createRouter, createWebHistory } from "vue-router";

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

当使用这种历史模式时,URL 会看起来很 "正常",例如 https://example.com/user/id。漂亮!

不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id,就会得到一个 404 错误。这就尴尬了。

不用担心:要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。漂亮依旧!

缺点:会增加访问服务器的次数

不同的历史模式 | HTML5 模式 | 服务器配置示例

不过要注意的是,服务器将不再会报 404 错误,你可以使用复杂的麻烦的配置在服务端实现所有 url 的匹配,然后将其余的返回 404

举例部分:

  • NGINX
nginx
location / {
  try_files $uri $uri/ /index.html;
}

单个页面配置的属性

ts
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      // 路径
      path: "/home", // 默认匹配 /home /HomE /home/
      // 路由名称,可以通过路由名称打开路由组件
      name: "home",
      // 路径别名,对应的路径也会打开该路由组件
      alias: "/home",
      // 对应的路由组件
      component: () => import("@/views/HomeView.vue"),
      // 严格检查后缀斜杠,默认false
      strict: true,
      // 严格检查大小写,默认false
      sensitive: true,
    },
  ],
  // 全局使用严格检查
  strict: true,
  sensitive: true,
});

动态路由匹配

静态不讲了,有手就行

ts
const routes = [
  // 普通的匹配
  { path: "/home" },
  // 匹配/home /HomE (不分大小写) /home/
];

动态路由匹配用于:

  • url 地址是动态的(比如各个用户的主页 url)

  • 需要获取 url 里面的参数

基础用法

ts
// router/index
const routes = [
  // 动态匹配
  { path: "/users/:username" },
];

// xxxx.vue <js>
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
console.log(route.params);
// 动态路由中匹配到的字符串会解析到这个对象中,字符串数组
// eg:{
// 		user:"truraly"
// }

正则复杂用法

ts
const routes = [
  // 动态匹配
  { path: "/users/:username" },
  // 匹配/users/truraly
  // {username:"truraly"}

  { path: "/users/:username/blog/:bid" },
  // 匹配/users/truraly/blog/123
  // {username:"truraly",bid:"123"}

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

  // 使用正则规定路径
  { psth: "/users/:uid(\\d+)" }, // 注意双斜杠
  // 匹配/users/114514
  // 只匹配数字,且不能为空
  // { uid: 114514 }

  //
  { path: "/users/:uid(\\d+)*" },
  // 匹配/users /users/123 /users/123/456
  // 匹配多个数字串
  // { uid: [123, 456] }

  //
  { path: "/users/user-:username(\\w+)" },
  // 匹配 /users/user-truraly /users/user-xiabeize
  // { username: "truraly" }

  // 常用于404页面
  { path: "/path404(*)*" },
  // 匹配其他字符串匹配不到的
  // /asda/asd
  // { path404: [ "asda", "asd" ] }

  // 其实这种也能用与404,会有一些区别
  { path: "/path404(*)" },
  // 匹配其他字符串匹配不到的
  // /asda/asd
  // { path404: "asda/asd" }
];

使用 vue-router 对加载的影响

使用 vue 路由时需要注意,跳转页面时相同的组件不会重新加载

比如从一个人的博客跳转到另一个人的博客时,它可能不会按你想象得那样刷新

解决方法:

ts
// plan 1
// xxxx.vue
// 使用watch监视url的变化
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
this.$watch(
  () => route.params,
  (toParams, previousParams) => {
    // 对路由变化做出响应...
  }
);

// plan 2
// router/index.ts
// 使用 beforeRouteUpdate 导航守卫
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: "/blog/:bid",
      // ...
      async beforeRouteUpdate(to, from) {
        // 对路由变化做出响应...
        let bid = await fetchUser(to.params.bid);
      },
    },
  ],
});

children | 嵌套路由

/user/johnny/profile                  /user/johnny/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

在 APP.vue 之外的组件上使用<router-view></router-view>根据 url 的情况选择性加载部分页面

比如用户博客界面和用户信息界面,可以有部分一样的

vue
// 父组件
<div class="user">
	<h2>User {{ $route.params.id }}</h2>
	<router-view></router-view>
</div>
ts
// 该父组件的路由配置
{
    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,
        },
        {
            // 以/开头的嵌套路径将被视为根路径,即无视父路径匹配/users
            // UserList 将被渲染到 User 的 <router-view> 内部
            path: '/users',
            compenent: UserList
        },
        {
            // 当 /user/:id 匹配成功
            path: '',
            compenent: User-detail
        }
    ]
}
  • 当子路由被命名时,/user/:id将始终匹配到子路由
ts
const routes = [
  {
    path: "/user/:id",
    component: User,
    // 请注意,只有子路由具有名称
    children: [{ path: "", name: "user", component: UserHome }],
  },
];
  • 当父子路由都命名时,可以用父路由的名字来导航到父路由(不显示子路由)

    但刷新页面时,会显示嵌套路由,因为这被视为访问了url:/user/:id而不是name:user-parent

ts
const routes = [
  {
    path: "/user/:id",
    name: "user-parent",
    component: User,
    children: [{ path: "", name: "user", component: UserHome }],
  },
];

路由的声明式和编程式使用方法

ts
// 获取RouterLink(声明式)
import { RouterLink } from "vue-router";

// 获取router(编程式)
import { useRouter } from "vue-router";
const router = useRouter();

编程式路由底层是对浏览器 windows.history 的一个封装?

差不多

RouterLink 和 router.push()是向 history 栈里添加记录,可以返回

router.replace()是替换栈顶,被替换的网页不能返回

html
<RouterLink :to="...">
  <!-- 跳转到一个命名路由 -->
  <router-link :to="{ name: 'user', params: { username: 'erina' }}">
    User
  </router-link></RouterLink
>

router.push() | 编程式

ts
// 跳转到对应的url
router.push();

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

path参数存在时,params会被忽略

ts
// `params` 不能与 `path` 一起使用
router.push({ path: "/user", params: { username } }); // -> /user

也可以使用字符串拼接出完整的path路径

ts
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

当指定 params 时,可提供 stringnumber 参数(或者对于可重复的参数可提供一个数组)。任何其他类型(如 undefinedfalse 等)都将被自动字符串化。对于可选参数,你可以提供一个空字符串("")来跳过它。

router.replace() | 编程式

用法和router.push()一样,甚至可以用router.push()代替(在传递给 router.pushrouteLocation 中增加一个属性 replace: true

router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })

router.go() | 编程式

类似window.history.go(n)

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

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

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

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

name | 命名路由

优点:

  • 没有硬编码的 URL
  • params 的自动编码/解码。
  • 防止你在 url 中出现打字错误。
  • 绕过路径排序(如显示一个)
ts
const routes = [
  {
    path: "/user/:username",
    name: "user",
    component: User,
  },
];

name | 命名视图

使用并列而不是嵌套的方法摆放几个路由

vue
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<!-- name不声明则默认为defult -->
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>

路由配置(注意使用 components而不是 component)

ts
const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: "/",
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
});

案例 | 嵌套命名试图

/settings/emails                                       /settings/profile
+-----------------------------------+                  +------------------------------+
| UserSettings                      |                  | UserSettings                 |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | |  +------------>  | | Nav | UserProfile        | |
| |     +-------------------------+ |                  | |     +--------------------+ |
| |     |                         | |                  | |     | UserProfilePreview | |
| +-----+-------------------------+ |                  | +-----+--------------------+ |
+-----------------------------------+                  +------------------------------+
  • Nav 只是一个常规组件。
  • UserSettings 是一个视图组件。
  • UserEmailsSubscriptionsUserProfileUserProfilePreview 是嵌套的视图组件。
vue
<!-- UserSettings.vue -->
<div>
  <h1>User Settings</h1>
  <NavBar />
  <router-view />
  <router-view name="helper" />
</div>
ts
// router
{
  path: '/settings',
  // 你也可以在顶级路由就配置命名视图
  component: UserSettings,
  children: [{
    path: 'emails',
    component: UserEmailsSubscriptions
  }, {
    path: 'profile',
    components: {
      default: UserProfile,
      helper: UserProfilePreview
    }
  }]
}

redirect | 重定向

ts
// 从 /home 重定向到 /
const routes = [{ path: "/home", redirect: "/" }];
// 重定向的目标也可以是一个命名的路由
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",
    // ...
  },
];

请注意,导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。在上面的例子中,在 /home 路由中添加 beforeEnter 守卫不会有任何效果。

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

相对重定向

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

alias | 别名

ts
//  将/ 别名为 /home,意味着当用户访问 /home 时,URL 仍然是 /home,但会被匹配为用户正在访问 /。
const routes = [{ path: "/", component: Homepage, alias: "/home" }];

//
const routes = [
  {
    path: "/users",
    component: UsersLayout,
    children: [
      // 为这 3 个 URL 呈现 UserList
      // - /users
      // - /users/list
      // - /people
      // 使用数组提供多个别名
      { path: "", component: UserList, alias: ["/people", "list"] },
    ],
  },
];

如果你的路由有参数,请确保在任何绝对别名中包含它们:

ts
const routes = [
  {
    path: "/users/:id",
    component: UsersByIdLayout,
    children: [
      // 为这 3 个 URL 呈现 UserDetails
      // - /users/24
      // - /users/24/profile
      // - /24
      { path: "profile", component: UserDetails, alias: ["/:id", ""] },
    ],
  },
];

props | 路由组件传参

vue-router 中 props 有 3 种模式,分别将 3 种对象传入组件中

布尔模式

props 设置为 true 时,route.params 将被设置为组件的 props。

vue
<!-- xxx.vue -->
<script setup lang="ts">
const props = defineProps(["uid"]);
</script>
<template>
  <h1>{{ uid }}</h1>
</template>
ts
// router
const routes = [
  {
    // 参数名称要相同
    path: "/user/:uid",
    component: User,
    // 注意这个属性不能落下,不写template里面就不能用
    props: true,
  },
];

对于有命名视图的路由,你必须为每个命名视图定义 props 配置:

ts
const routes = [
  {
    path: "/user/:id",
    components: { default: User, sidebar: Sidebar },
    props: { default: true, sidebar: false },
  },
];

对象模式

props 是一个对象时,它将原样设置为组件 props。当 props 是静态的时候很有用。

ts
const routes = [
  {
    path: "/promotion/from-newsletter",
    component: Promotion,
    props: { newsletterPopup: false },
  },
];

函数模式

你可以创建一个返回 props 的函数。这允许你将参数转换为其他类型,将静态值与基于路由的值相结合等等。

ts
const routes = [
  {
    path: "/search",
    component: SearchUser,
    props: (route) => ({ query: route.query.q }),
  },
];

URL /search?q=vue 将传递 {query: 'vue'} 作为 props 传给 SearchUser 组件。

Copyright © 2022 田园幻想乡 浙ICP备2021038778号-1