从零开始理解 VueRouter,看这一篇就够了

1,089 阅读7分钟

什么是路由

什么是路由?在 Web 开发中,路由是指根据不同的 URL 地址,展示不同的内容或页面。

你可以把它想象成一个导航系统,它会根据你输入的地址,把你带到你想去的地方。

现在,想象你正在开发一个单页应用,这是一个在一个页面上完成所有操作的应用,好像是一个连续的电影。但这并不意味着你的应用只有一个视图或页面,你仍然可以在应用内部展示不同的内容。

这就是路由的作用,它可以让你在不刷新整个页面的情况下,改变视图。

Vue Router 是如何工作的?

1、定义路由

首先,你需要定义你的路由。每个路由应该映射到一个组件。

组件是 Vue.js 的一个重要概念,你可以把它想象成一个可以复用的代码块,它可以负责一个特定的视图或功能。在定义路由时,你需要指定路由的路径和对应的组件。

2、创建路由实例

一旦你定义了路由,你就可以创建一个 Vue Router 实例。

在创建实例时,你需要把路由配置传给它。

3、使用路由实例

你需要在你的 Vue 应用中使用这个 Vue Router 实例。

这样,你的应用就能根据不同的 URL 地址展示不同的内容了。

两种模式 Hash 与 History

这两种模式都是 Vue Router 控制 URL 变化,以便匹配正确路由的方式。

它们都是浏览器提供的方式,添加、删除记录都不会引起页面的刷新。

Hash 模式

这种模式你应该很熟悉,早在 AJAX 时代,我们就已经开始使用这种模式来实现前端路由了。简单来说,Hash 模式就是利用了 URL 中的 hash(#)来进行路由跳转。

你应该见过类似 http://www.abc.com/#/user 这样的 URL,其中 #/user 就是 hash 值。浏览器会自动对 hash 值进行监听,当 hash 值发生变化时,就知道路由发生了改变,然后就会根据新的 hash 值来更新页面。

而且,由于 hash 值实际上是用来定位页面内部的某个元素的(比如说,你写了一个 <a href="#top">回到顶部</a>,点击这个链接,页面就会滚动到 id 为 top 的元素那里),所以改变 hash 值并不会引起页面的刷新,也不会向服务器发送请求。

这种模式的优点是兼容性好,即使是很老的浏览器都支持。但缺点是看起来不太美观。

History 模式

这是 HTML5 新引入的一个特性,叫做 History API。相比于 Hash 模式,History 模式的 URL 看起来更像是正常的 URL,比如 http://www.abc.com/user

History API 提供了很多方法,比如 history.pushState()history.replaceState(),它们可以让我们自由地修改浏览器的历史记录,而且不会引起页面的刷新。

这种模式的优点是 URL 看起来更美观,更像是正常的 URL。

但缺点是并不是所有浏览器都支持这个特性,比如说 IE9 及其以下的版本就不支持。

另外,使用这种模式的话,可能需要后端的配合,因为当用户直接访问 http://www.abc.com/user 这样的 URL 时,如果后端没有对应的处理(比如返回一个通用的 HTML 文件),那么用户就会看到 404 错误。

router-view 与嵌套路由

router-view 是一个 Vue Router 提供的特殊组件,它会根据当前的路由动态渲染出不同的组件。换句话说,你可以把 router-view 看作是一个“容器”,它会自动填充上与当前路由相对应的组件的内容。通常,你会在你的主应用组件(或者 layout 组件)中使用一个 router-view。

在大型应用中,你可能有多个 layout 组件,比如一个主页面 layout,一个管理员页面 layout,等等。每个 layout 可能有自己的顶部导航、侧边栏等。在这种情况下,你可以在每个 layout 组件中使用 <router-view>,然后使用嵌套路由来根据当前路由选择正确的子组件来渲染。

嵌套路由允许我们构建嵌套视图的应用。例如,你可能有一个包含侧边栏的布局,侧边栏中又有链接到其他页面的链接。在这种情况下,你需要根据当前的路由来动态改变侧边栏中的内容。这就是嵌套路由的一个用例。

因此,<router-view /> 不是嵌套路由本身,但是在实现嵌套路由时,你需要在父路由的组件模板中使用 <router-view /> 组件。

我们直接来举例说明吧:

首先,让我们来创建一个布局组件,我们称之为 Layout.vue。这个布局组件包含了一个侧边栏和一个 <router-view /> 组件:

<template>
  <div>
    <div class="sidebar">
      <router-link to="/foo">Foo</router-link>
      <router-link to="/bar">Bar</router-link>
    </div>
    <div class="content">
      <router-view />
    </div>
  </div>
</template>

在上面的代码中,<router-link> 组件会生成一个链接,点击这个链接会改变当前的路由。而 <router-view /> 组件则会渲染当前路由匹配的组件。

然后,让我们在路由配置中定义一些路由,这些路由使用 Layout.vue 作为它们的布局:

const routes = [
  {
    path: '/foo',
    component: Layout,
    children: [
      {
        path: '',
        component: Foo
      }
    ]
  },
  {
    path: '/bar',
    component: Layout,
    children: [
      {
        path: '',
        component: Bar
      }
    ]
  }
]

在上面的代码中,我们定义了两个路由,/foo/bar。这两个路由都使用 Layout 组件作为它们的布局,然后在布局中的 <router-view /> 的位置渲染 Foo 组件和 Bar 组件。

当你访问 /foo 的时候,Vue Router 会匹配 /foo 路由,然后渲染 Layout 组件。在 Layout 组件的模板中,<router-view /> 的位置会被替换为 Foo 组件。

当你点击侧边栏中的链接,导航到 /bar 的时候,Vue Router 会匹配 /bar 路由,然后重新渲染 Layout 组件。这次,在 Layout 组件的模板中,<router-view /> 的位置会被替换为 Bar 组件。

因此,通过使用 <router-view /> 组件和嵌套路由,你可以在同一个布局中动态地切换不同的内容,而侧边栏则保持不变。这就是嵌套路由的一个常见用例。

路由参数

动态路由参数

如果你有一个显示用户信息的页面,你可能希望 URL 是 /user/:id,其中 :id 是用户的 ID。在这个例子中,:id 就是一个动态路由参数,它的值可以根据你访问的 URL 来改变。

在你的组件中,你可以通过 this.$route.params.id 来访问这个参数的值。例如,如果你访问 /user/123,那么 this.$route.params.id 的值就会是 "123"。

查询参数

查询参数是 URL 中 ? 后面的部分,它们不会影响路由的匹配,但你可以在你的组件中通过 this.$route.query 访问它们。

例如,如果你访问 /user/123?foo=bar,那么你可以通过 this.$route.params.id 访问 "123",并通过 this.$route.query.foo 访问 "bar"。

通配符参数

例如,如果你定义了一个路由 { path: '/user/*', component: User },那么 /user/123/user/foo/user/foo/bar 都会匹配这个路由。

User 组件中,你可以通过 this.$route.params.pathMatch 访问通配符参数的值。例如,如果你访问 /user/foo/bar,那么 this.$route.params.pathMatch 的值会是 "foo/bar"。

命名路由参数

如果你的 URL 结构比较复杂,或者你想在 URL 中传递更多的信息,你可以使用命名路由参数。

命名路由参数允许你在一个路径中定义多个参数。

例如,你可以定义一个路由 { path: '/user/:userId/post/:postId', component: UserPost }

在这个例子中,如果你访问 /user/123/post/456,那么在 UserPost 组件中,你可以通过 this.$route.params.userId 访问 "123",并通过 this.$route.params.postId 访问 "456"。

路由导航守卫

在 Vue Router 中,路由导航守卫主要用于监听路由的变化,然后在路由改变之前或之后做一些事情。

例如,你可能想在用户导航到需要登录的页面之前检查用户是否已经登录,如果没有,那么你可以重定向用户到登录页面。

全局前置守卫

这个守卫在路由发生改变之前被触发。

让我们假设我们有一个需求:如果用户没有登录,我们不希望他们访问任何页面,而是将他们重定向到登录页面。这可以通过全局前置守卫实现:

router.beforeEach((to, from, next) => {
  // 假设 `isLoggedIn` 是一个方法,用于检查用户是否登录
  if (!isLoggedIn() && to.path !== '/login') {
    next('/login');
  } else {
    next();
  }
});

在这个钩子函数中,你可以通过调用 next 函数来决定是否允许导航。例如,你可以通过 next(false) 来取消导航,或者通过 next('/login') 来重定向到登录页面。

路由独享的守卫

这个守卫只对特定路由有效。

例如,我们可能有一些特定的页面,只有管理员可以访问。我们可以在这些路由上添加一个前置守卫,来检查用户是否具有管理员权限:

// user.js
export function isAdmin() {
  // 这里实现你的逻辑,例如检查用户的角色是否为 'admin'
  return getUserRole() === 'admin';
}

// router.js
import { isAdmin } from './user.js';

const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      if (!isAdmin()) {
        next('/not-authorized');
      } else {
        next();
      }
    }
  }
];

组件内的守卫

有时候,我们可能想要在进入或离开特定的组件时做一些事情。

例如,我们可能有一个编辑页面,我们希望当用户离开这个页面时,提示他们保存更改:

export default {
  data() {
    return {
      hasChanges: false,
    };
  },
  beforeRouteLeave(to, from, next) {
    if (this.hasChanges) {
      if (confirm('You have unsaved changes, do you want to leave?')) {
        next();
      } else {
        next(false);
      }
    } else {
      next();
    }
  }
};

这个 beforeRouteLeave 守卫会在用户试图离开这个组件时被触发。

如果 hasChangestrue,那么我们会弹出一个确认框,问用户是否确定离开。如果用户点击确定,我们就允许他们离开,否则我们取消导航。

最后我们用面试题做一下自测,看看真的掌握了没有

解释一下Vue路由的作用是什么?

Vue路由(vue-router)是Vue.js官方的路由库,它用于构建单页面应用(SPA)。通过Vue路由,我们可以将应用的不同视图映射到对应的URL,实现页面之间的导航,而无需页面刷新。

你能解释一下Vue路由的两种模式:hash模式和history模式吗?

Vue路由有两种模式:hash模式和history模式。

hash模式是基于URL的hash(#符号)来实现的,当URL的hash值改变时,页面不会刷新,我们可以监听hashchange事件来实现页面的跳转。

这种模式的优点是兼容性好,可以在所有支持JavaScript的浏览器上运行。

而history模式则是基于HTML5 History API中的pushState和replaceState方法来实现的,它可以使我们的应用在没有hash的情况下也能正常工作。

但这种模式需要服务器的配合,服务器需要对所有的同源请求返回相同的HTML文件。

能解释一下Vue路由的导航守卫吗?它们有什么用?

Vue路由的导航守卫是一些钩子函数,它们会在路由发生变化时被调用。

导航守卫有全局守卫、路由独享的守卫和组件内的守卫三种类型。

我们可以在导航守卫中执行一些逻辑,例如验证用户是否登录,如果用户未登录则跳转到登录页面。这样可以实现对路由的访问控制。

如何实现Vue路由的懒加载?

Vue路由的懒加载可以通过异步组件和Webpack的代码分割功能来实现。

我们可以在路由配置中,将需要懒加载的路由的component选项设置为一个能够返回Promise的函数,这个Promise应该resolve一个组件。例如:

const Foo = () => import('./Foo.vue')

这样做的好处是,我们可以将代码分割成多个小的bundle,当路由被访问的时候才加载对应的bundle,这可以提高首屏加载速度,优化用户体验。

什么是嵌套路由,如何使用?

嵌套路由是指一个路由下面还有其他的子路由。

我们可以在路由配置中,通过children选项来定义嵌套路由。当父路由被访问时,父路由组件的<router-view>标签中。例如:

const router = new VueRouter({
  routes: [
    {
      path: '/user/:id',
      component: User,
      children: [
        {
          path: 'profile',
          component: UserProfile
        },
        {
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

在这个例子中,当访问/user/:id/profile时,UserProfile组件会被渲染到User组件的<router-view>标签中。

如何在Vue路由中传递参数?

在Vue路由中,我们可以通过以下两种方式传递参数:使用动态路由和使用query参数。

动态路由是在路由路径中使用冒号(:)标记一个参数,例如/user/:id。当这个路由被访问时,id的值可以通过$route.params.id获取。

query参数是在URL后面使用问号(?)加上参数名和参数值的形式,例如/search?query=JavaScript。这些参数可以通过$route.query对象获取。

何时使用beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave导航守卫?

这三个导航守卫都是在组件内部定义的。

beforeRouteEnter在路由进入之前调用,常用于获取数据。此时还没创建组件实例,所以this不能用。

beforeRouteUpdate在当前路由改变,但是该组件被复用时调用。例如,对于一个带有动态参数的路由/foo/:id,当我们从/foo/1导航到/foo/2时,由于两个路由都对应同一个组件,所以这个组件不会被销毁重建,而是被复用,beforeRouteUpdate将在这个过程中被调用。

beforeRouteLeave在路由离开该组件的时候被调用,常用于离开确认,比如编辑页面未保存,提醒用户保存。