Vue Router 4.x

思维导图

简介

Vue RouterVue 官方指定路由,其赋能 Vue 实现 单页应用(SPA,Single Page Application) 前端路由功能。

本文主要介绍下 Vue Router 4.x 的相关使用方法。

基本使用

下面通过一个小例子驱动阐述如何在 Vue3 下使用 Vue Router 4.x。

例子:假设当前页面有两个标签:/home/me,要求点击不同的标签分别显示不同的组件页面。

思路:使用 Vue Router 配置相关路由,点击标签时,跳转到对应路由视图。具体操作步骤如下:

  1. 创建项目:首先创建一个示例项目:

    # 此处使用 vite 进行构建
    $ npm init vite@latest vue3_demos --template vue
    
    $ cd vue3_demos
    
    # 安装相关依赖
    $ npm install
    
    # 启动应用
    $ npm run dev
    

    :Vue3 安装更多方法,可参考:Vue3 安装

  2. 依赖引入:导入 Vue Router 依赖库:

    $ npm install vue-router@4
    
  3. 创建组件:分别创键Home.vueMe.vue两个组件:

    <!-- file: components/Home.vue -->
    <template>
      <h1>Home Component</h1>
    </template>
    
    <style scoped>
    h1 {
      background-color: green;
    }
    </style>
    
    <!-- file: components/Me.vue -->
    <template>
      <h1>Me Component</h1>
    </template>
    
    <style scoped>
    h1 {
      background-color: yellow;
    }
    </style>
    
  4. 创建并配置路由对象:新建router/index.js,在此创建并配置路由对象:

    // file: router/index.js
    // 导入相关路由组件对象
    import Home from '../components/Home.vue';
    import Me from '../components/Me.vue';
    
    // 定义路由映射:路由映射到具体组件
    const routes = [
      // 根路径 / 重定向到 /home
      {
        path: '/',
        redirect: '/home',
      },
      // 前端路由 /home 对应组件 Home
      {
        path: '/home',
        component: Home,
      },
      // 前端路由 /me 对应组件 Me
      {
        path: '/me',
        component: Me,
      },
    ];
    
    // 导入相关函数
    import { createRouter, createWebHashHistory } from 'vue-router';
    
    // 创建路由实例(`router`)并传递路由映射配置(`route`)
    const router = createRouter({
      // 配置导航模式,此处采用 hash 模式
      history: createWebHashHistory(),
      routes,
    });
    
    // 导出 router 实例
    export default router;
    
  5. 装载 Router 实例:创建全局Vue实例,并装载已配置的 Vue Router 实例:

    // file: main.js
    
    import { createApp } from 'vue';
    import App from './App.vue';
    
    import router from './router/index.js';
    
    const app = createApp(App);
    // 装载 Vue Router 实例,确保整个 Vue 应用全局支持路由
    app.use(router);
    app.mount('#app');
    
  6. 主页面配置路由导航:主页面通过<router-link>可配置路由导航,匹配的组件会最终被渲染到<router-view>中:

    <!-- file: App.vue -->
    <template>
      <h1>Main Page</h1>
      <div class="nav">
        <!-- router-link 最终会被渲染为一个 a 标签 -->
        <router-link to="/home">Home</router-link>
        <router-link to="/me">Me</router-link>
      </div>
      <!-- 路由出口:匹配组件最终被渲染位置 -->
      <router-view />
    </template>
    
    <style scoped>
    .nav {
      width: 100px;
      display: flex;
      justify-content: space-around;
    }
    </style>
    

以上,就是一个简单的路由导航示例,其效果如下所示:


简单路由示例

功能介绍

下面会对 Vue Router 4.x 提供的一些常见功能进行简介。

路由对象

Vue Router 中存在两个最主要的路由对象为:

  1. Router:表示 Vue Router 实例对象。

    在 Vue Router 4.x 中,使用的是createRouter()函数创建Router实例:

    import { createRouter, createWebHashHistory } from 'vue-router';
    const routes = [...];
    
    // 创建路由实例(`router`)并传递路由映射配置(`route`)
    const router = createRouter({
      history: createWebHashHistory(),
      routes,
    });
    
    export default router;
    

    Router主要是提供了对历史记录栈的操作功能,比如Router#push方法可以往历史堆栈中推入一个新的 URL,Router#replace方法可用于替换当前的 URL,还有Router#forwardRouter#backRouter#go...

    :当代码中调用createApp().use(Router)时,其实就向 Vue 实例全局中注入了一个Router实例,代码中获取该Router实例的方法有如下几种:

    1. Options API:选项式 API 可通过this.$routers获取全局路由实例
    2. Composition API:组合式 API 可通过函数useRouter()获取全局路由实例
    3. template:模板中可通过$router获取全局路由实例

    :创建Router时,可设置一个激活样式linkActiveClass,这样在主页选中<router-link>时,对应标签就会被添加上自定义激活样式:

    // file: router/index.js
    const router = createRouter({
      history: createWebHashHistory(),
      routes,
      // 设置标签激活时,添加样式类为 activeLink
      linkActiveClass: 'activeLink',
    });
    
    <!-- file: App.vue -->
    <template>
      <h1>Main Page</h1>
    
      <!-- 点击选中标签时,自动添加 activeLink 类名 -->
      <router-link to="/home">Home</router-link>
      <router-link to="/me">Me</router-link>
    
      <router-view />
    </template>
    <style scoped>
    /* 设置选中样式 */
    .activeLink {
      background-color: red;
    }
    </style>
    
  2. RouteLocationNormalized:表示当前路由记录实例。

    routes中配置的每条路由映射,我们称之为「路由记录」,其类型就是RouteLocationNormalized

    const routes = [
      { path: '/home', component: () => import('@/components/Home.vue'), },
      { path: '/me', component: Me, },
      { path: '/user/:id*', component: () => import('@/components/User.vue'), },
    ];
    

    当在主页上点击选中相应路由标签时,就会跳转到相应路由映射组件,此时可以通过Router#currentRoute得到当前路由记录。比如,点击跳转到/user时,Router#currentRoute就可以获取/user路由相关配置信息。

    其实还有其他更方便的方法获取到当前路由地址实例,主要包含如下:

    1. Options API:对于选项式 API,可通过this.$route获取当前路由地址实例
    2. Composition API:对于组合式 API,可通过useRoute()函数获取当前路由地址实例
    3. template:模板中可通过$route获取当前路由地址实例

    RouteLocationNormalized提供了丰富的路由配置选项,这里列举一些比较常用的:

    • hashstring类型,表示当前路由hash部分。总是以#开头,如果 URL 中没有hash,则为空字符串。

    • pathstring类型,表示当前路由路径,形如/user/1

    • fullpathstring类型,表示当前路由完整路径,包含pathqueryhash部分,

    • name:类型为RouteRecordName | null | undefined,表示当前路由名称。

      :建议为每个路由对象命名,方便后续编程导航。名称命名需唯一。

      const routes = [
        {
          name: 'user', // 路由命名
          path: '/user/:id',
          component: () => import('@/components/User.vue'),
        },
      ];
      
    • redirectedFrom:类型为RouteLocation | undefined,表示触发重定向的路由地址。

    • params:类型为RouteParams,用于获取路径参数。比如对于/user/:id$route.params获取到的就是id对应的信息。

    • query:类型为LocationQuery,表示 URL 查询参数。形如/user?id=1,则query对应的就是{id: 1}

    • meta:类型为RouteMeta,表示对当前路由的元数据,即额外信息描述。

      const routes = [
        {
          meta: { name: 'Whyn' },  // 路由元数据
          path: '/user/:id',
          component: () => import('@/components/User.vue'),
        },
      ];
      

历史记录模式

前端路由的改变,其核心是不会向服务器发出请求,Vue Router 提供了两种模式支持该功能:

  • Hash 模式:Hash 模式 URL 构成中带有一个哈希字符#,更改#字符后面的路径不会发送请求给服务器,其底层是基于浏览器的windows.onhashchange事件。

    Hash 模式的优点是编程简单,缺点是路径不够简洁(多了额外字符#),且对 SEO 不够友好。

    Vue Router 中通过createWebHashHistory()函数配置 Hash 模式:

    import { createRouter, createWebHashHistory } from 'vue-router'
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes: [
        //...
      ],
    })
    
  • History 模式:History 模式利用了 HTML5 History Interface 中新增的pushState()replaceState()等方法,实现了浏览器的历史记录栈。调用这些方法修改 URL 历史记录时,不会触发浏览器向后端发送请求。

    History 模式的优点是路径简洁且控制更灵活,缺点是 History 模式下,用户在浏览器中直接访问或手动刷新时,会触发真正的请求,服务器没有当前请求资源时,会返回一个404错误。解决的方法也很简单,就是后端添加一个回退路由,在 URL 匹配不到任何静态资源时,默认返回前端页面的index.html。比如,Nginx 中可以配置如下:

    location / {
      try_files $uri $uri/ /index.html;
    }
    

    Vue Router 中通过createWebHistory()函数配置启动 History 模式:

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

最后,Vue Router 更推荐使用 History 模式。

路由懒加载

前端每个路由都会对应一个组件,前面我们使用的方式都是导入相应组件,然后配置映射到对应路由中:

const Home = { template: '<div>Home</div>' }
const routes = [
  { path: '/', component: Home },
]

当路由比较多时,会导致加载的组件也变多,这样在应用打包后,生成的 JavaScript 包会臃肿变大,影响页面加载效率。

因此,Vue Router 提供了 路由懒加载 功能,其将不同路由对应的组件分割成不同的代码块,然后当路由被访问时,才动态加载对应组件,这样效率就会更高。

Vue Router 支持开箱即用的动态导入,如下所示:

// 直接加载
import Home from '@/components/Home.vue';
// 懒加载
const Me = () => import('@/components/Me.vue')

const routes = [
  { path: '/home', component: Home, }, // 直接加载
  { path: '/me', component: Me },      // 懒加载
];

:上述代码使用@代表src目录,使能需要进行如下配置:

// file: vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

const path = require('path');
export default defineConfig({
  plugins: [vue()],
  resolve: {
    // 别名配置
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});

此时如果执行构建:

$ npm run build
dist/index.html                  0.48 KiB
dist/assets/index.4ada91f0.js    2.13 KiB / gzip: 1.13 KiB
dist/assets/Me.a2557100.js       0.23 KiB / gzip: 0.20 KiB # Me.vue
dist/assets/index.d8be49df.css   0.12 KiB / gzip: 0.12 KiB
dist/assets/Me.0aae35d5.css      0.04 KiB / gzip: 0.06 KiB # Me.vue
dist/assets/vendor.fd7d0278.js   71.78 KiB / gzip: 28.44 KiB

可以看到,配置懒加载组件Me.vue会被单独打包到一个.js文件中。

实际上,Vue Router 中,懒加载基本原理是:componentcomponents配置接收的是一个返回Promise组件的函数,因此,我们也可以进行自定义动态导入,其实就是创建一个返回Promise的函数,该Promise返回一个组件,比如:

const UserDetails = () =>
  Promise.resolve({
    /* 组件定义 */
  })

动态路由

一个很常见的场景,比如,根据用户id获取用户信息,通常对应的 RESTFul API 为/user/{id},即匹配/user/1/user/2...

Vue Router 将这种形式的 URL 称之为 动态路由,其使用:进行使能,形式如下所示:

const User = {
  template: '<div>User</div>',
}

// 这些都会传递给 `createRouter`
const routes = [
  // 动态段以冒号开始
  { path: '/user/:id', component: User },
]

此时,上述path可以匹配/user/1/user/username等等。

动态路由也支持正则匹配,可以设置更加精细匹配规则,常见匹配设置如下:

const routes = [
  // /:id -> 仅匹配数字
  { path: '/:id(\\d+)' },

  // /:username -> 匹配其他任何内容
  { path: '/:username' },

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

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

  // 匹配 /users 和 /users/posva
  { path: '/users/:userId?' },

  // 匹配 /, /1, /1/2, 等
  { path: '/:chapters(\\d+)*' },
]

动态路由信息可以通过$route.params进行获取,一个示例代码如下:
配置路径/user/:id(\d+)映射到组件User.vue,并展示id具体值:

<!-- file: components/User.vue -->
<template>
  <!-- 模板获取 route.params 属性 -->
  <h1>User Component: {{ $route.params }}</h1>
</template>

<script>
import { onBeforeRouteUpdate } from 'vue-router';

export default {
  name: 'User',
  setup(props, context) {
    // Router 钩子函数
    onBeforeRouteUpdate((to, from, next) => {
      // 代码获取 route.params 属性
      console.log(to.params.id);
      next();
    });
  },
};
</script>

// file: router/index.js
const routes = [
  {
    path: '/user/:id(\\d+)',
    component: () => import('@/components/User.vue'),
  },
];

<!-- file: App.vue -->
<template>
  <h1>Main Page</h1>
  <div class="nav">
    <router-link :to="'/user/' + id" @click="randomId">User</router-link>
  </div>
  <router-view />
</template>

<script >
import { ref } from '@vue/reactivity';
export default {
  name: 'App',
  setup(props, context) {
    const id = ref(1);

    function randomInRange(min, max) {
      return Math.floor(Math.random() * (max - min)) + min;
    }

    const randomId = () => (id.value = randomInRange(0, 100));

    return {
      id,
      randomId,
    };
  },
};
</script>

:动态路由如果使用可变参数进行修饰,则:

  • /user/:id**表示匹配 0 个或多个,此时的$params.id为数组形式,即{id: []}
  • /user/:id++表示匹配 1 个或多个,此时的$params.id为数组形式,即{id: []}
  • /user/:id??表示匹配 0 个或 1 个,此时的$params.id为值,即{id: 10}

:由于动态路由实际上映射的是同一个组件,因此,在进行动态路由切换时,会复用该组件实例,所以组件生命周期钩子不会被调用,如果想监听动态路由改变,需要手动watch当前路由this.$route.params上对应的属性,或者使用导航守卫钩子函数,比如onBeforeRouteUpdate进行监听。

嵌套路由

  • 嵌套路由:就是一个路由内部可以嵌套一些子路由。

比如,/user是一个路由,/user/one/user/two/user下的两个子路由,所以/user是一个嵌套了/user/one/user/two的嵌套路由。

再简单进行理解,一个路由对应一个组件,因此,嵌套路由其实就是一个父组件内部包含了多个子组件,且这些子组件也是通过路由进行访问。

嵌套路由的配置很简单,只需为父路由设置children属性即可,下面以一个例子进行驱动,阐述嵌套路由。

示例:假设现在有一个新闻版块,该版块内部含有两个子版块,分别为财经版块和体育版块,用代码进行实现。

分析:父路由对应组件news.vue,其内嵌套两个子路由/news/finance/news/sports,分别对应两个组件Finance.vueSports.vue

嵌套路由搭建步骤如下:

  1. 首先创建所有对应组件:

    <!-- file: components/nested_router/Finance.vue -->
    <template>
      <h3>Finance Component</h3>
    </template>
    
    <!-- file: components/nested_router/Sports.vue -->
    <template>
      <h3>Sports Component</h3>
    </template>
    
    <!-- file: components/nested_router/News.vue -->
    <template>
      <h1>News Component</h1>
    
      <div class="nav">
        <router-link to="/news/finance">Finance</router-link>
        <router-link to="/news/sports">Sports</router-link>
      </div>
      <router-view />
    </template>
    
    <style scoped>
    /* router-link 最终会被转换为 a 标签 */
    .nav a {
      margin-left: 10px;
    }
    </style>
    

    News.vue由于是父组件,因此其内部包含router-view标签用于展示嵌套子路由页面组件。

  2. 配置路由信息:

    // file: ./router/nested.js
    import { createRouter, createWebHistory } from 'vue-router';
    
    const routes = [
      {
        path: '/news',
        component: () => import('@/components/nested_router/News.vue'),
        // 配置嵌套路由
        children: [
          {
            // /news 重定向到 /news/finance
            path: '/news',
            redirect: '/news/finance',
          },
          {
            // /news/finace
            path: 'finance',
            component: () => import('@/components/nested_router/Finance.vue'),
          },
          {
            // /news/sports
            path: 'sports',
            component: () => import('@/components/nested_router/Sports.vue'),
          },
        ],
      },
    ];
    
    export default createRouter({
      // 使用 History 模式
      history: createWebHistory(),
      routes,
    });
    
  3. 主页面添加展示/news映射的组件New.vue

    // file: main.js
    import { createApp } from 'vue';
    import App from './App.vue';
    
    import router from './router/nested.js';
    
    const app = createApp(App);
    app.use(router);
    app.mount('#app');
    
    <!-- file: App.vue -->
    <template>
      <h1>Main Page</h1>
      <router-link to="/news">News</router-link>
      <!-- 路由出口:匹配组件最终被渲染位置 -->
      <router-view />
    </template>
    

编程式导航

前面我们都是通过<router-link>标签实现导航链接功能,实际上当我们点击<router-link>时,其内部会调用Router#push方法实现真正的路由导航,因此,我们也可以直接通过编程方式,即调用Router相关方式,手动实现导航跳转。

Vue Router 主要提供了以下几个方法供我们实现路由跳转:

  • Router#push:该方法会向历史堆栈添加一个新记录,可供我们导航到指定的 URL。

    Router#push<router-link>底层默认调用的导航方法:

    声明式 编程式
    <router-link :to="..."> router.push(...)

    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' })
    

    <router-link>中的to属性与Router#push方法接收的参数类型一致,所以上述配置也适用于to属性。

    :如果同时提供了pathparams,则params会被忽略。建议使用name属性替换path,避免潜在问题:

    router.push({ name: 'Me' });
    
  • Router#replace:该方法同样支持路由导航,但是与Router#push不同的是,该方法不会向历史堆栈中添加导航记录,而是直接替换当前导航记录。
    一般只有当明确禁止跳转回前一个路由时,才会使用该方法。

    <router-link>可通过添加replace属性,使能Router#replace

    声明式 编程式
    <router-link :to="..." replace> router.replace(...)

    Router#push也可以实现replace功能,只需为路由添加replace: true属性:

    router.push({ path: '/home', replace: true })
    // 相当于
    router.replace({ path: '/home' })
    
  • Router#go:该方法可以横跨跳转历史堆栈:

    // 向前移动一条记录,与 router.forward() 相同
    router.go(1)
    
    // 返回一条记录,与router.back() 相同
    router.go(-1)
    
    // 前进 3 条记录
    router.go(3)
    
    // 如果没有那么多记录,静默失败
    router.go(-100)
    router.go(100)
    

下面还是通过一个示例驱动进行讲解。

例子:比如主页有两个按钮,要求点击两个按钮显示Home.vueMe.vue两个页面。

思路:/home路由映射组件Home.vue/me路由映射组件Me.vue,然后为按钮添加点击事件,通过调用Router相关方式实现路由跳转。
具体步骤如下:

  1. 创建路由页面Home.vueMe.vue。内容参考上文

  2. 配置路由信息:

    // file: router/index.js
    import { createRouter, createWebHistory } from 'vue-router';
    
    export default createRouter({
      history: createWebHistory(),
      routes: [
        {
          name: 'Home',
          path: '/home',
          component: () => import('@/components/Home.vue'),
        },
        {
          name: 'Me',
          path: '/me',
          component: () => import('@/components/Me.vue'),
        },
      ],
    });
    
  3. 加载 Vue Router 并配置主页面:

    // file: main.js
    import { createApp } from 'vue';
    import App from './App.vue';
    
    import router from './router/index.js';
    
    const app = createApp(App);
    // 装载 Vue Router 实例,确保整个 Vue 应用全局支持路由
    app.use(router);
    app.mount('#app');
    
    <!-- file: App.vue -->
    <template>
      <h1>Main Page</h1>
    
      <div class="nav">
          <button @click="nav2Home">Home</button>
          <button @click="nav2Me">Me</button>
      </div>
    
      <router-view />
    </template>
    
    <script setup>
    import { useRouter } from "vue-router"
    
    const router = useRouter();
    const nav2Home = () => router.push('/home');
    const nav2Me = () => router.push('/me');
    
    </script>
    
    <style scoped>
    .nav button {
        margin-left: 10px;
    }
    </style>
    

运行结果如下图所示:


programmatic_navigation_demo

命名路由

前面很多处都提到了 命名路由,其实就是为路由配置一个name属性:

const routes = [
  {
    name: 'user', # 命名路由
    path: '/user/:username',
    component: User
  }
]

使用命名路由,除了避免了手动硬编码 URL 外,最大的好处就是它会对params属性自动进行编码/解码。

具体使用命名路由时,只需为<router-link>to属性指定一个命名对象即可:

<router-link :to="{ name: 'user', params: { username: 'erina' }}"> User </router-link>

命名视图

一个<router-view>只能显示一个组件页面,即使是嵌套路由,同一时刻也只是显示一个组件页面。但是如果一个路由需要同时显示两个及以上组件页面,此时就需要同时提供多个<router-view>,并且为<router-view>设置相应名称,路由配置时会指定相应组件显示到对应名称的<router-view>上,这种具备名称的的<router-view>称之为 命名视图

只需为<router-view>设置name属性,即为 命名视图

<router-view name="sidebar" />

:实际上,所有的<router-view>都是命名视图,未配置name属性时,其默认名为default

举个例子:比如现在主页上有两个组件页面:导航栏sidebar和主区域main,同时呈现在主页上,要求使用命名视图完成。

思路:主页需要配置两个命名视图的<router-view>,然后路由配置时,指定相应组件显示到对应的命名视图上即可。
具体步骤如下:

  1. 创建导航栏组件和主区域组件:

    <!-- components/named_view/SideBar.vue -->
    <template>
        <nav>
            <u>
                <li>Nav 1</li>
                <li>Nav 2</li>
            </u>
        </nav>
    </template>
    
    <style scoped>
    nav {
        background-color: green;
    }
    </style>
    
    
    <!-- components/named_view/Main.vue -->
    <template>
        <p>Main Content</p>
    </template>
    
    <style scoped>
    p {
        background-color: yellow;
    }
    </style>
    
  2. 配置路由信息:根路由需要配置两个子组件,并指定各自要显示到的命名视图:

    // file: router/index.js
    import { createRouter, createWebHistory } from 'vue-router';
    
    export default createRouter({
      history: createWebHistory(),
      routes: [
        {
          path: '/',
          // 主页面同时显示多个路由映射组件
          components: {
            // Main.vue 显示到 default 视图上
            default: () => import('@/components/named_view/Main.vue'),
            // SideBar.vue 显示到 sidebar 视图上
            sidebar: () => import('@/components/named_view/SideBar.vue'),
          },
        },
      ],
    });
    
  3. 主页配置两个命名视图,分别渲染对应的组件:

    // file: main.js 参考上文
    
    <!-- file: App.vue -->
    <template>
      <h1>Main Page</h1>
      
      <!-- 点击跳转到根路由 -->
      <router-link to="/">Main</router-link>
      
      <router-view name="sidebar" />
      <router-view />
      
    </template>
    

效果如下:


Named View Demo

重定向

  • 重定向:就是当访问一个路由时,会被拦截并重新导航到另一个路由中。

重定向只需通过配置$route即可,Vue Router 大致提供如下几种类型重定向配置:

  • path:直接配置重定向路径:

    // 将 /home 重定向到 /
    const routes = [{ path: '/home', redirect: '/' }]
    
  • 命名路由:命名路由本质是一个路由,因此也可直接命名路由,最终重定向到该命名路由对应的path

    // 将 /home 重定向到命名路由 homepage 
    const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
    
  • 函数:可以将redirect设置会一个函数,动态返回重定向地址信息:

    const routes = [
      {
        path: '/a',
        redirect: (to) => {
          // 参数 to 是源路由地址实例对象
          const { hash, params, query } = to;
          if (query.to === 'search') {
            // 返回一个路由映射配置信息
            return { path: '/search', query: { q: params.searchText }};
          }
          if (params.id) {
            // 返回动态路由
            return '/with-params/:id';
          } else {
            // 返回路由地址
            return '/bar';
          }
        },
      },
    ];
    

别名

  • 别名:Vue Router 中,别名 指的是一个路由对应两个路径,即访问这两个路径都能跳转到该路由。

比如对于下面的配置:

const routes = [
  {
    path: '/user_one', // 路径
    alias: '/user_1',  // 别名
    component: User,
  },
];

其实就是为/user_one设置了一个别名/user_1,此时访问/user_one/user_1都可以导航到User.vue组件。

:如果想同时指定多个别名,则需进行如下配置:

const routes = [
  {
    path: '/user_one', 
    component: User,
    alias: ['/user_1', '/user_yi'], // 使用数组即可
  },
];

路由组件传参

  • 路由组件传参:其实就是跳转时,给路由映射组件传递参数。

前面内容,在模板中,我们都是通过$route直接获取路由信息:

<!-- file: components/User.vue -->
<template>
  <h1>User Component: {{ $route.params.name }}</h1>
</template>

// file: router/index.js
const routes = [
  {
    path: '/user/:name',
    component: () => import('@/components/User.vue'),
  },
];

这种做法其实紧耦合了路由与组件,对组件复用性产生消极影响。

一个更灵活的解决办法是通过为组件动态传递参数,Vue Router 大致提供了如下几种路由参数传递方法:

  • 布尔模式:为路由地址映射配置一个props: true,此时route.params会被传递给组件的props

    比如,上面的例子使用参数传递,可修改为如下:

    // file: router/index.js
    const routes = [
      {
        path: '/user/:name',
        component: () => import('@/components/User.vue'),
        props: true, // 使能路由参数传递
      },
    ];
    
    <!-- file: components/User.vue -->
    <template>
      <!-- 显示传递的参数 -->
      <h1>User Component: {{ name }}</h1>
    </template>
    
    <script>
    export default {
      name: 'User',
      props: {
        name: String, // 接收传递的参数
      },
    };
    </script>
    
  • 命名视图:对于有命名视图的路由,必须显示为每个命名视图定义props配置:

    const routes = [
      {
        path: '/user/:name',
        components: { default: User, sidebar: Sidebar },
        // default 视图使能参数传递,sidebar 视图关闭参数传递
        props: { default: true, sidebar: false }
      }
    ]
    
  • 对象模式:将props设置为对象,直接传递该对象给组件:

    const routes = [
      {
        path: '/user/:name',
        components: { default: User, sidebar: Sidebar },
        // 直接传递对象
        props: { name: 'Whyn' }
      }
    ]
    

    :对象模式此时直接传递props对象,与路由params没有关系。当我们需要传递静态内容给到路由组件时,对象模式就很合适。

  • 函数模式:可以将props设置为一个函数,该函数参数是路由地址信息$route,因此可以在函数内部提取当前路由相关信息,结合自己一些静态内容,组合进行设置,更加灵活:

    const routes = [
      {
        path: '/user',
        component: () => import('@/components/User.vue'),
        props: (route) => ({ query: route.query.name }),
      },
    ];
    

    上述配置中,比如此时我们跳转到/user?name=Whyn时,则会传递{ query: 'Whyn' }给到User.vue组件:

    <!-- file: components/User.vue -->
    <template>
      <h1>User Component: {{ query }}</h1>
    </template>
    
    <script>
    export default {
      name: 'User',
      props: {
        query: String,
      },
    };
    </script>
    

导航守卫

  • 导航守卫:所谓 导航守卫,其实就是一些路由钩子,在进行路由跳转或取消时,会触发相应钩子,我们可以在这些钩子中,检测路由跳转及获取相关数据,植入我们自己的操作。

Vue Router 总共提供了如下三种类型导航守卫:

  • 全局守卫:可以监控所有路由跳转的导航守卫。具体可在细分为如下三种类型:

    1. 全局前置守卫:当触发一个导航跳转时,全局前置守卫会被触发调用(多个全局前置守卫会按照创建顺序依次回调)。

      可通过Router#beforeEach注册一个全局前置守卫:

      const router = createRouter({ ... })
      
      router.beforeEach((to, from, next) => {
        // ...
      })
      

      全局前置守卫是异步解析执行的,即回调函数与asyncPromise工作方式一样:

      // 上述代码相当于如下
      router.beforeEach(async (to, from, next) => {
        // canUserAccess() 返回 `true` 或 `false`
        return await canUserAccess(to)
      })
      

      全局前置守卫每个守卫方法可接受三个参数:

      • to:表示即将要进入的路由目标
      • from:当前导航正要离开的路由对象
      • next:可选参数,用于触发并传递额外数据给下一个路由对象:
      router.beforeEach((to, from, next) => {
          // 进行管道的下一个钩子
          next(); 
      
          // 中断当前导航,URL 重置为 from 路由路径
          next(false);
      
          // 跳转到指定路由 /home
          next('/home');
      
          // 同 next('/home')
          next( {path: '/home'} );
      
          // 如果 next 参数是一个 Error 实例,则导航会被终止,
          // 且参数 error 会被传递给 Router#onError() 回调
          next(error);
      })
      

      全局前置守卫每个守卫方法返回值有如下几种可选:

      • true|undefined:返回trueundefined,表示导航有效,并自动调用下一个导航守卫:

        router.beforeEach((to, from) => {
            // 没有 return,则表示 return undefined
            return false;
        });
        

        :如果显示声明了第三个参数next,则必须手动调用next进行跳转:

        router.beforeEach((to, from, next) => {
            // false
            next(true);
            // 或者 undefined
            next();
        });
        
      • false:表示取消导航跳转,即当前 URL 重置到from路由地址

        router.beforeEach((to, from) => {
            // 取消导航,停留在 from 路由页面
            return false;
        });
        
      • 一个路由地址:指定导航跳转地址,相当于调用了Router#push

        const routes = [
          // ...
          { path: '/user', component: () => import('@/components/User.vue') },
        ],
        
        router.beforeEach((to, from) => {
          if (to.path === '/user') {
              return true;
            return '/user';
        });
        
      • Error:抛出异常,此时会取消导航并回调Router#onError全局错误钩子:

        router.beforeEach((to, from, next) => {
          // 手动抛异常,会被 onError 捕获到
          throw 'error occured!!';
        });
        
        router.onError((error, to, from) => {
          console.log(error, to, from);
        });
        
    2. 全局解析守卫:全局解析守卫与全局前置守卫类似,都是在 每次导航 时都会被触发,但是全局解析守卫会确保在 所有组件内守卫和异步路由组件被解析后,才会被正确调用

      可通过Router#beforeResolve方法注册一个全局解析守卫:

      router.beforeResolve((to, from, next) => {
        console.log('Router beforeResolve');
      });
      

      Router#beforeResolve是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。

    3. 全局后置钩子:全局后置钩子是在导航被确认后,才进行调用,因此,该钩子不会携带next函数,也不会改变导航状态。

      可通过Router#afterEach注册一个全局后置钩子:

      router.afterEach((to, from, failure) => {
        console.log('Router afterEach');
      });
      

      Router#afterEach对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。

  • 路由独享守卫:如果只关注特定路由导航跳转事件,那么只需为相应路由添加导航守卫即可。

    只需在路由配置上定义beforeEnter函数即可注册一个路由独享导航守卫:

    const routes = [
      {
        path: '/home',
        component: () => import('@/components/Home.vue'),
        // 路由独享守卫
        beforeEnter: (to, from, next) => {
          console.log('Home: Route beforeEnter');
          next();
        },
        // 支持传递多个钩子函数
        // beforeEnter: [(..)=>{..}, (..)=>{..}],
      },
    ];
    

    beforeEnter只在进入路由时触发,更改paramsqueryhash时,不会触发beforeEnter

  • 组件内守卫:一个路由最终映射为一个组件,因此我们也可以在组件内定义导航守卫,捕获路由跳转导航相关信息。

    组件内守卫在 Options API 中可通过为组件添加beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave函数进行注册:

    const UserDetails = {
      template: `...`,
      beforeRouteEnter(to, from) {
        // 在渲染该组件的对应路由被验证前调用
        // 不能获取组件实例 `this` !
        // 因为当守卫执行时,组件实例还没被创建!
      },
      beforeRouteUpdate(to, from) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
        // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
      },
      beforeRouteLeave(to, from) {
        // 在导航离开渲染该组件的对应路由时调用
        // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
      },
    }
    

    组件内守卫在 Composition API 中可通过在setup函数中定义onBeforeRouteUpdateonBeforeRouteLeave分别注册 update 和 leave 守卫:

    <script>
    import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router';
    export default {
      name: 'Home',
    
      setup(props, context) {
        console.log('setup = onBeforeRouteEnter');
        // update 守卫确认导航跳转,因此没有 next 参数
        onBeforeRouteUpdate((fto, from) => {
          console.log('onBeforeRouteUpdate');
        });
    
        // leave 守卫同样没有 next 参数
        onBeforeRouteLeave((to, from) => {
          console.log('onBeforeRouteLeave');
        });
      },
    };
    </script>
    

    Composition API 中,setup等于beforeRouteEnter,在路由进入时被触发;onBeforeRouteUpdate在第一次路由进入时,不会被触发,只有在该路由重复进入,组件被复用时才会被触发(比如params的改变);onBeforeRouteLeave在离开当前路由时会被触发。

最后,全局导航守卫、路由独享守卫和组件内守卫完整的导航解析流程如下图所示:

:流程图来源于网上,侵删。

路由导航完整解析流程

附录

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Technology vue 3 安装 npm install @vue/cli vue-router 4 安装 ...
    行者深蓝阅读 1,215评论 0 0
  • 1. 后端路由阶段 早期的网站开发整个HTML页面是由服务器来渲染的。服务器直接生产渲染好对应的HTML页面, 返...
    itlu阅读 528评论 0 3
  • 一、vue-router实现原理 SPA(single page application):单一页面应用程序,只有...
    walycode阅读 1,036评论 1 3
  • 怎么重定向页面? 第一种方法: 第二种方法:const router = new VueRouter({route...
    chendalei阅读 1,153评论 0 0
  • 学习目的 学习Vue的必备技能,必须 熟练使用 Vue-router,能够在实际项目中运用。 Vue-rout...
    _1633_阅读 91,139评论 3 58