Vue Router 4.x

2021-11-25  本文已影响0人  Whyn
思维导图

简介

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 提供了两种模式支持该功能:

最后,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>

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

:由于动态路由实际上映射的是同一个组件,因此,在进行动态路由切换时,会复用该组件实例,所以组件生命周期钩子不会被调用,如果想监听动态路由改变,需要手动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 主要提供了以下几个方法供我们实现路由跳转:

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

例子:比如主页有两个按钮,要求点击两个按钮显示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 大致提供如下几种类型重定向配置:

别名

比如对于下面的配置:

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 大致提供了如下几种路由参数传递方法:

导航守卫

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

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

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

路由导航完整解析流程

附录

参考

上一篇 下一篇

猜你喜欢

热点阅读