美文网首页
Vue Router 4.x

Vue Router 4.x

作者: Whyn | 来源:发表于2021-11-25 22:20 被阅读0次
    思维导图

    简介

    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在离开当前路由时会被触发。

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

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

    路由导航完整解析流程

    附录

    参考

    相关文章

      网友评论

          本文标题:Vue Router 4.x

          本文链接:https://www.haomeiwen.com/subject/uambtrtx.html