Vue Router 4.x

思维导图

简介

Vue Router 是 Vue 官方指定路由,其赋能 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两个组件:

    
    
    
    
    
    
    
    
    
    
  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. 主页面配置路由导航:主页面通过可配置路由导航,匹配的组件会最终被渲染到中:

    
    
    
    
    

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


简单路由示例

功能介绍

下面会对 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,这样在主页选中时,对应标签就会被添加上自定义激活样式:

    // file: router/index.js
    const router = createRouter({
      history: createWebHashHistory(),
      routes,
      // 设置标签激活时,添加样式类为 activeLink
      linkActiveClass: 'activeLink',
    });
    
    
    
    
    
  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: '
Home
' } 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: '
User
', } // 这些都会传递给 `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: router/index.js
const routes = [
  {
    path: '/user/:id(\\d+)',
    component: () => import('@/components/User.vue'),
  },
];





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

  • /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. 首先创建所有对应组件:

    
    
    
    
    
    
    
    
    
    
    

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

编程式导航

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

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

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

    Router#push底层默认调用的导航方法:

    声明式 编程式
    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' })
    

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

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

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

    可通过添加replace属性,使能Router#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');
    
    
    
    
    
    
    
    

运行结果如下图所示:


programmatic_navigation_demo

命名路由

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

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

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

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

 User 

命名视图

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

只需为设置name属性,即为 命名视图


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

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

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

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

    
    
    
    
    
    
    
    
    
    
    
  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 参考上文
    
    
    
    

效果如下:


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: 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, // 使能路由参数传递
      },
    ];
    
    
    
    
    
    
  • 命名视图:对于有命名视图的路由,必须显示为每个命名视图定义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组件:

    
    
    
    
    

导航守卫

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

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 守卫:

    
    

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

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

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

路由导航完整解析流程

附录

  • 本篇博文 Demo 源码可查看:vue-router4.x-demo

参考

  • 官网文档
  • 可能比文档还详细--VueRouter完全指北

你可能感兴趣的:(Vue Router 4.x)