Vue Router 4.x

思維導(dǎo)圖

簡(jiǎn)介

Vue RouterVue 官方指定路由,其賦能 Vue 實(shí)現(xiàn) 單頁(yè)應(yīng)用(SPA,Single Page Application) 前端路由功能。

本文主要介紹下 Vue Router 4.x 的相關(guān)使用方法。

基本使用

下面通過(guò)一個(gè)小例子驅(qū)動(dòng)闡述如何在 Vue3 下使用 Vue Router 4.x。

例子:假設(shè)當(dāng)前頁(yè)面有兩個(gè)標(biāo)簽:/home/me,要求點(diǎn)擊不同的標(biāo)簽分別顯示不同的組件頁(yè)面。

思路:使用 Vue Router 配置相關(guān)路由,點(diǎn)擊標(biāo)簽時(shí),跳轉(zhuǎn)到對(duì)應(yīng)路由視圖。具體操作步驟如下:

  1. 創(chuàng)建項(xiàng)目:首先創(chuàng)建一個(gè)示例項(xiàng)目:

    # 此處使用 vite 進(jìn)行構(gòu)建
    $ npm init vite@latest vue3_demos --template vue
    
    $ cd vue3_demos
    
    # 安裝相關(guān)依賴
    $ npm install
    
    # 啟動(dòng)應(yīng)用
    $ npm run dev
    

    :Vue3 安裝更多方法,可參考:Vue3 安裝

  2. 依賴引入:導(dǎo)入 Vue Router 依賴庫(kù):

    $ npm install vue-router@4
    
  3. 創(chuàng)建組件:分別創(chuàng)鍵Home.vueMe.vue兩個(gè)組件:

    <!-- 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. 創(chuàng)建并配置路由對(duì)象:新建router/index.js,在此創(chuàng)建并配置路由對(duì)象:

    // file: router/index.js
    // 導(dǎo)入相關(guān)路由組件對(duì)象
    import Home from '../components/Home.vue';
    import Me from '../components/Me.vue';
    
    // 定義路由映射:路由映射到具體組件
    const routes = [
      // 根路徑 / 重定向到 /home
      {
        path: '/',
        redirect: '/home',
      },
      // 前端路由 /home 對(duì)應(yīng)組件 Home
      {
        path: '/home',
        component: Home,
      },
      // 前端路由 /me 對(duì)應(yīng)組件 Me
      {
        path: '/me',
        component: Me,
      },
    ];
    
    // 導(dǎo)入相關(guān)函數(shù)
    import { createRouter, createWebHashHistory } from 'vue-router';
    
    // 創(chuàng)建路由實(shí)例(`router`)并傳遞路由映射配置(`route`)
    const router = createRouter({
      // 配置導(dǎo)航模式,此處采用 hash 模式
      history: createWebHashHistory(),
      routes,
    });
    
    // 導(dǎo)出 router 實(shí)例
    export default router;
    
  5. 裝載 Router 實(shí)例:創(chuàng)建全局Vue實(shí)例,并裝載已配置的 Vue Router 實(shí)例:

    // file: main.js
    
    import { createApp } from 'vue';
    import App from './App.vue';
    
    import router from './router/index.js';
    
    const app = createApp(App);
    // 裝載 Vue Router 實(shí)例,確保整個(gè) Vue 應(yīng)用全局支持路由
    app.use(router);
    app.mount('#app');
    
  6. 主頁(yè)面配置路由導(dǎo)航:主頁(yè)面通過(guò)<router-link>可配置路由導(dǎo)航,匹配的組件會(huì)最終被渲染到<router-view>中:

    <!-- file: App.vue -->
    <template>
      <h1>Main Page</h1>
      <div class="nav">
        <!-- router-link 最終會(huì)被渲染為一個(gè) a 標(biāo)簽 -->
        <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>
    

以上,就是一個(gè)簡(jiǎn)單的路由導(dǎo)航示例,其效果如下所示:


簡(jiǎn)單路由示例

功能介紹

下面會(huì)對(duì) Vue Router 4.x 提供的一些常見功能進(jìn)行簡(jiǎn)介。

路由對(duì)象

Vue Router 中存在兩個(gè)最主要的路由對(duì)象為:

  1. Router:表示 Vue Router 實(shí)例對(duì)象。

    在 Vue Router 4.x 中,使用的是createRouter()函數(shù)創(chuàng)建Router實(shí)例:

    import { createRouter, createWebHashHistory } from 'vue-router';
    const routes = [...];
    
    // 創(chuàng)建路由實(shí)例(`router`)并傳遞路由映射配置(`route`)
    const router = createRouter({
      history: createWebHashHistory(),
      routes,
    });
    
    export default router;
    

    Router主要是提供了對(duì)歷史記錄棧的操作功能,比如Router#push方法可以往歷史堆棧中推入一個(gè)新的 URL,Router#replace方法可用于替換當(dāng)前的 URL,還有Router#forward、Router#back、Router#go...

    :當(dāng)代碼中調(diào)用createApp().use(Router)時(shí),其實(shí)就向 Vue 實(shí)例全局中注入了一個(gè)Router實(shí)例,代碼中獲取該Router實(shí)例的方法有如下幾種:

    1. Options API:選項(xiàng)式 API 可通過(guò)this.$routers獲取全局路由實(shí)例
    2. Composition API:組合式 API 可通過(guò)函數(shù)useRouter()獲取全局路由實(shí)例
    3. template:模板中可通過(guò)$router獲取全局路由實(shí)例

    :創(chuàng)建Router時(shí),可設(shè)置一個(gè)激活樣式linkActiveClass,這樣在主頁(yè)選中<router-link>時(shí),對(duì)應(yīng)標(biāo)簽就會(huì)被添加上自定義激活樣式:

    // file: router/index.js
    const router = createRouter({
      history: createWebHashHistory(),
      routes,
      // 設(shè)置標(biāo)簽激活時(shí),添加樣式類為 activeLink
      linkActiveClass: 'activeLink',
    });
    
    <!-- file: App.vue -->
    <template>
      <h1>Main Page</h1>
    
      <!-- 點(diǎn)擊選中標(biāo)簽時(shí),自動(dòng)添加 activeLink 類名 -->
      <router-link to="/home">Home</router-link>
      <router-link to="/me">Me</router-link>
    
      <router-view />
    </template>
    <style scoped>
    /* 設(shè)置選中樣式 */
    .activeLink {
      background-color: red;
    }
    </style>
    
  2. RouteLocationNormalized:表示當(dāng)前路由記錄實(shí)例。

    routes中配置的每條路由映射,我們稱之為「路由記錄」,其類型就是RouteLocationNormalized

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

    當(dāng)在主頁(yè)上點(diǎn)擊選中相應(yīng)路由標(biāo)簽時(shí),就會(huì)跳轉(zhuǎn)到相應(yīng)路由映射組件,此時(shí)可以通過(guò)Router#currentRoute得到當(dāng)前路由記錄。比如,點(diǎn)擊跳轉(zhuǎn)到/user時(shí),Router#currentRoute就可以獲取/user路由相關(guān)配置信息。

    其實(shí)還有其他更方便的方法獲取到當(dāng)前路由地址實(shí)例,主要包含如下:

    1. Options API:對(duì)于選項(xiàng)式 API,可通過(guò)this.$route獲取當(dāng)前路由地址實(shí)例
    2. Composition API:對(duì)于組合式 API,可通過(guò)useRoute()函數(shù)獲取當(dāng)前路由地址實(shí)例
    3. template:模板中可通過(guò)$route獲取當(dāng)前路由地址實(shí)例

    RouteLocationNormalized提供了豐富的路由配置選項(xiàng),這里列舉一些比較常用的:

    • hashstring類型,表示當(dāng)前路由hash部分??偸且?code>#開頭,如果 URL 中沒有hash,則為空字符串。

    • pathstring類型,表示當(dāng)前路由路徑,形如/user/1

    • fullpathstring類型,表示當(dāng)前路由完整路徑,包含path、queryhash部分,

    • name:類型為RouteRecordName | null | undefined,表示當(dāng)前路由名稱。

      :建議為每個(gè)路由對(duì)象命名,方便后續(xù)編程導(dǎo)航。名稱命名需唯一。

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

    • params:類型為RouteParams,用于獲取路徑參數(shù)。比如對(duì)于/user/:id,$route.params獲取到的就是id對(duì)應(yīng)的信息。

    • query:類型為LocationQuery,表示 URL 查詢參數(shù)。形如/user?id=1,則query對(duì)應(yīng)的就是{id: 1}

    • meta:類型為RouteMeta,表示對(duì)當(dāng)前路由的元數(shù)據(jù),即額外信息描述。

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

歷史記錄模式

前端路由的改變,其核心是不會(huì)向服務(wù)器發(fā)出請(qǐng)求,Vue Router 提供了兩種模式支持該功能:

  • Hash 模式:Hash 模式 URL 構(gòu)成中帶有一個(gè)哈希字符#,更改#字符后面的路徑不會(huì)發(fā)送請(qǐng)求給服務(wù)器,其底層是基于瀏覽器的windows.onhashchange事件。

    Hash 模式的優(yōu)點(diǎn)是編程簡(jiǎn)單,缺點(diǎn)是路徑不夠簡(jiǎn)潔(多了額外字符#),且對(duì) SEO 不夠友好。

    Vue Router 中通過(guò)createWebHashHistory()函數(shù)配置 Hash 模式:

    import { createRouter, createWebHashHistory } from 'vue-router'
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes: [
        //...
      ],
    })
    
  • History 模式:History 模式利用了 HTML5 History Interface 中新增的pushState()replaceState()等方法,實(shí)現(xiàn)了瀏覽器的歷史記錄棧。調(diào)用這些方法修改 URL 歷史記錄時(shí),不會(huì)觸發(fā)瀏覽器向后端發(fā)送請(qǐng)求。

    History 模式的優(yōu)點(diǎn)是路徑簡(jiǎn)潔且控制更靈活,缺點(diǎn)是 History 模式下,用戶在瀏覽器中直接訪問或手動(dòng)刷新時(shí),會(huì)觸發(fā)真正的請(qǐng)求,服務(wù)器沒有當(dāng)前請(qǐng)求資源時(shí),會(huì)返回一個(gè)404錯(cuò)誤。解決的方法也很簡(jiǎn)單,就是后端添加一個(gè)回退路由,在 URL 匹配不到任何靜態(tài)資源時(shí),默認(rèn)返回前端頁(yè)面的index.html。比如,Nginx 中可以配置如下:

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

    Vue Router 中通過(guò)createWebHistory()函數(shù)配置啟動(dòng) History 模式:

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

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

路由懶加載

前端每個(gè)路由都會(huì)對(duì)應(yīng)一個(gè)組件,前面我們使用的方式都是導(dǎo)入相應(yīng)組件,然后配置映射到對(duì)應(yīng)路由中:

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

當(dāng)路由比較多時(shí),會(huì)導(dǎo)致加載的組件也變多,這樣在應(yīng)用打包后,生成的 JavaScript 包會(huì)臃腫變大,影響頁(yè)面加載效率。

因此,Vue Router 提供了 路由懶加載 功能,其將不同路由對(duì)應(yīng)的組件分割成不同的代碼塊,然后當(dāng)路由被訪問時(shí),才動(dòng)態(tài)加載對(duì)應(yīng)組件,這樣效率就會(huì)更高。

Vue Router 支持開箱即用的動(dòng)態(tài)導(dǎo)入,如下所示:

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

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

:上述代碼使用@代表src目錄,使能需要進(jìn)行如下配置:

// 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'),
    },
  },
});

此時(shí)如果執(zhí)行構(gòu)建:

$ 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會(huì)被單獨(dú)打包到一個(gè).js文件中。

實(shí)際上,Vue Router 中,懶加載基本原理是:componentcomponents配置接收的是一個(gè)返回Promise組件的函數(shù),因此,我們也可以進(jìn)行自定義動(dòng)態(tài)導(dǎo)入,其實(shí)就是創(chuàng)建一個(gè)返回Promise的函數(shù),該Promise返回一個(gè)組件,比如:

const UserDetails = () =>
  Promise.resolve({
    /* 組件定義 */
  })

動(dòng)態(tài)路由

一個(gè)很常見的場(chǎng)景,比如,根據(jù)用戶id獲取用戶信息,通常對(duì)應(yīng)的 RESTFul API 為/user/{id},即匹配/user/1、/user/2...

Vue Router 將這種形式的 URL 稱之為 動(dòng)態(tài)路由,其使用:進(jìn)行使能,形式如下所示:

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

// 這些都會(huì)傳遞給 `createRouter`
const routes = [
  // 動(dòng)態(tài)段以冒號(hào)開始
  { path: '/user/:id', component: User },
]

此時(shí),上述path可以匹配/user/1、/user/username等等。

動(dòng)態(tài)路由也支持正則匹配,可以設(shè)置更加精細(xì)匹配規(guī)則,常見匹配設(shè)置如下:

const routes = [
  // /:id -> 僅匹配數(shù)字
  { path: '/:id(\\d+)' },

  // /:username -> 匹配其他任何內(nèi)容
  { 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+)*' },
]

動(dòng)態(tài)路由信息可以通過(guò)$route.params進(jìn)行獲取,一個(gè)示例代碼如下:
配置路徑/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 鉤子函數(shù)
    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>

:動(dòng)態(tài)路由如果使用可變參數(shù)進(jìn)行修飾,則:

  • /user/:id**表示匹配 0 個(gè)或多個(gè),此時(shí)的$params.id為數(shù)組形式,即{id: []}
  • /user/:id++表示匹配 1 個(gè)或多個(gè),此時(shí)的$params.id為數(shù)組形式,即{id: []}
  • /user/:id??表示匹配 0 個(gè)或 1 個(gè),此時(shí)的$params.id為值,即{id: 10}

:由于動(dòng)態(tài)路由實(shí)際上映射的是同一個(gè)組件,因此,在進(jìn)行動(dòng)態(tài)路由切換時(shí),會(huì)復(fù)用該組件實(shí)例,所以組件生命周期鉤子不會(huì)被調(diào)用,如果想監(jiān)聽動(dòng)態(tài)路由改變,需要手動(dòng)watch當(dāng)前路由this.$route.params上對(duì)應(yīng)的屬性,或者使用導(dǎo)航守衛(wèi)鉤子函數(shù),比如onBeforeRouteUpdate進(jìn)行監(jiān)聽。

嵌套路由

  • 嵌套路由:就是一個(gè)路由內(nèi)部可以嵌套一些子路由。

比如,/user是一個(gè)路由,/user/one/user/two/user下的兩個(gè)子路由,所以/user是一個(gè)嵌套了/user/one/user/two的嵌套路由。

再簡(jiǎn)單進(jìn)行理解,一個(gè)路由對(duì)應(yīng)一個(gè)組件,因此,嵌套路由其實(shí)就是一個(gè)父組件內(nèi)部包含了多個(gè)子組件,且這些子組件也是通過(guò)路由進(jìn)行訪問。

嵌套路由的配置很簡(jiǎn)單,只需為父路由設(shè)置children屬性即可,下面以一個(gè)例子進(jìn)行驅(qū)動(dòng),闡述嵌套路由。

示例:假設(shè)現(xiàn)在有一個(gè)新聞版塊,該版塊內(nèi)部含有兩個(gè)子版塊,分別為財(cái)經(jīng)版塊和體育版塊,用代碼進(jìn)行實(shí)現(xiàn)。

分析:父路由對(duì)應(yīng)組件news.vue,其內(nèi)嵌套兩個(gè)子路由/news/finance/news/sports,分別對(duì)應(yīng)兩個(gè)組件Finance.vueSports.vue。

嵌套路由搭建步驟如下:

  1. 首先創(chuàng)建所有對(duì)應(yīng)組件:

    <!-- 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 最終會(huì)被轉(zhuǎn)換為 a 標(biāo)簽 */
    .nav a {
      margin-left: 10px;
    }
    </style>
    

    News.vue由于是父組件,因此其內(nèi)部包含router-view標(biāo)簽用于展示嵌套子路由頁(yè)面組件。

  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. 主頁(yè)面添加展示/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>
    

編程式導(dǎo)航

前面我們都是通過(guò)<router-link>標(biāo)簽實(shí)現(xiàn)導(dǎo)航鏈接功能,實(shí)際上當(dāng)我們點(diǎn)擊<router-link>時(shí),其內(nèi)部會(huì)調(diào)用Router#push方法實(shí)現(xiàn)真正的路由導(dǎo)航,因此,我們也可以直接通過(guò)編程方式,即調(diào)用Router相關(guān)方式,手動(dòng)實(shí)現(xiàn)導(dǎo)航跳轉(zhuǎn)。

Vue Router 主要提供了以下幾個(gè)方法供我們實(shí)現(xiàn)路由跳轉(zhuǎn):

  • Router#push:該方法會(huì)向歷史堆棧添加一個(gè)新記錄,可供我們導(dǎo)航到指定的 URL。

    Router#push<router-link>底層默認(rèn)調(diào)用的導(dǎo)航方法:

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

    Router#push支持多種參數(shù)類型,其常見調(diào)用方式如下所示:

    // 字符串路徑
    router.push('/users/eduardo')
    
    // 帶有路徑的對(duì)象
    router.push({ path: '/users/eduardo' })
    
    // 命名的路由,并加上參數(shù),讓路由建立 url
    router.push({ name: 'user', params: { username: 'eduardo' } })
    
    // 帶查詢參數(shù),結(jié)果是 /register?plan=private
    router.push({ path: '/register', query: { plan: 'private' } })
    
    // 帶 hash,結(jié)果是 /about#team
    router.push({ path: '/about', hash: '#team' })
    

    <router-link>中的to屬性與Router#push方法接收的參數(shù)類型一致,所以上述配置也適用于to屬性。

    :如果同時(shí)提供了pathparams,則params會(huì)被忽略。建議使用name屬性替換path,避免潛在問題:

    router.push({ name: 'Me' });
    
  • Router#replace:該方法同樣支持路由導(dǎo)航,但是與Router#push不同的是,該方法不會(huì)向歷史堆棧中添加導(dǎo)航記錄,而是直接替換當(dāng)前導(dǎo)航記錄。
    一般只有當(dāng)明確禁止跳轉(zhuǎn)回前一個(gè)路由時(shí),才會(huì)使用該方法。

    <router-link>可通過(guò)添加replace屬性,使能Router#replace

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

    Router#push也可以實(shí)現(xiàn)replace功能,只需為路由添加replace: true屬性:

    router.push({ path: '/home', replace: true })
    // 相當(dāng)于
    router.replace({ path: '/home' })
    
  • Router#go:該方法可以橫跨跳轉(zhuǎn)歷史堆棧:

    // 向前移動(dòng)一條記錄,與 router.forward() 相同
    router.go(1)
    
    // 返回一條記錄,與router.back() 相同
    router.go(-1)
    
    // 前進(jìn) 3 條記錄
    router.go(3)
    
    // 如果沒有那么多記錄,靜默失敗
    router.go(-100)
    router.go(100)
    

下面還是通過(guò)一個(gè)示例驅(qū)動(dòng)進(jìn)行講解。

例子:比如主頁(yè)有兩個(gè)按鈕,要求點(diǎn)擊兩個(gè)按鈕顯示Home.vueMe.vue兩個(gè)頁(yè)面。

思路:/home路由映射組件Home.vue/me路由映射組件Me.vue,然后為按鈕添加點(diǎn)擊事件,通過(guò)調(diào)用Router相關(guān)方式實(shí)現(xiàn)路由跳轉(zhuǎn)。
具體步驟如下:

  1. 創(chuàng)建路由頁(yè)面Home.vueMe.vue。內(nèi)容參考上文

  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 并配置主頁(yè)面:

    // file: main.js
    import { createApp } from 'vue';
    import App from './App.vue';
    
    import router from './router/index.js';
    
    const app = createApp(App);
    // 裝載 Vue Router 實(shí)例,確保整個(gè) Vue 應(yīng)用全局支持路由
    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>
    

運(yùn)行結(jié)果如下圖所示:


programmatic_navigation_demo

命名路由

前面很多處都提到了 命名路由,其實(shí)就是為路由配置一個(gè)name屬性:

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

使用命名路由,除了避免了手動(dòng)硬編碼 URL 外,最大的好處就是它會(huì)對(duì)params屬性自動(dòng)進(jìn)行編碼/解碼。

具體使用命名路由時(shí),只需為<router-link>to屬性指定一個(gè)命名對(duì)象即可:

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

命名視圖

一個(gè)<router-view>只能顯示一個(gè)組件頁(yè)面,即使是嵌套路由,同一時(shí)刻也只是顯示一個(gè)組件頁(yè)面。但是如果一個(gè)路由需要同時(shí)顯示兩個(gè)及以上組件頁(yè)面,此時(shí)就需要同時(shí)提供多個(gè)<router-view>,并且為<router-view>設(shè)置相應(yīng)名稱,路由配置時(shí)會(huì)指定相應(yīng)組件顯示到對(duì)應(yīng)名稱的<router-view>上,這種具備名稱的的<router-view>稱之為 命名視圖。

只需為<router-view>設(shè)置name屬性,即為 命名視圖

<router-view name="sidebar" />

:實(shí)際上,所有的<router-view>都是命名視圖,未配置name屬性時(shí),其默認(rèn)名為default。

舉個(gè)例子:比如現(xiàn)在主頁(yè)上有兩個(gè)組件頁(yè)面:導(dǎo)航欄sidebar和主區(qū)域main,同時(shí)呈現(xiàn)在主頁(yè)上,要求使用命名視圖完成。

思路:主頁(yè)需要配置兩個(gè)命名視圖的<router-view>,然后路由配置時(shí),指定相應(yīng)組件顯示到對(duì)應(yīng)的命名視圖上即可。
具體步驟如下:

  1. 創(chuàng)建導(dǎo)航欄組件和主區(qū)域組件:

    <!-- 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. 配置路由信息:根路由需要配置兩個(gè)子組件,并指定各自要顯示到的命名視圖:

    // file: router/index.js
    import { createRouter, createWebHistory } from 'vue-router';
    
    export default createRouter({
      history: createWebHistory(),
      routes: [
        {
          path: '/',
          // 主頁(yè)面同時(shí)顯示多個(gè)路由映射組件
          components: {
            // Main.vue 顯示到 default 視圖上
            default: () => import('@/components/named_view/Main.vue'),
            // SideBar.vue 顯示到 sidebar 視圖上
            sidebar: () => import('@/components/named_view/SideBar.vue'),
          },
        },
      ],
    });
    
  3. 主頁(yè)配置兩個(gè)命名視圖,分別渲染對(duì)應(yīng)的組件:

    // file: main.js 參考上文
    
    <!-- file: App.vue -->
    <template>
      <h1>Main Page</h1>
      
      <!-- 點(diǎn)擊跳轉(zhuǎn)到根路由 -->
      <router-link to="/">Main</router-link>
      
      <router-view name="sidebar" />
      <router-view />
      
    </template>
    

效果如下:


Named View Demo

重定向

  • 重定向:就是當(dāng)訪問一個(gè)路由時(shí),會(huì)被攔截并重新導(dǎo)航到另一個(gè)路由中。

重定向只需通過(guò)配置$route即可,Vue Router 大致提供如下幾種類型重定向配置:

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

    // 將 /home 重定向到 /
    const routes = [{ path: '/home', redirect: '/' }]
    
  • 命名路由:命名路由本質(zhì)是一個(gè)路由,因此也可直接命名路由,最終重定向到該命名路由對(duì)應(yīng)的path

    // 將 /home 重定向到命名路由 homepage 
    const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
    
  • 函數(shù):可以將redirect設(shè)置會(huì)一個(gè)函數(shù),動(dòng)態(tài)返回重定向地址信息:

    const routes = [
      {
        path: '/a',
        redirect: (to) => {
          // 參數(shù) to 是源路由地址實(shí)例對(duì)象
          const { hash, params, query } = to;
          if (query.to === 'search') {
            // 返回一個(gè)路由映射配置信息
            return { path: '/search', query: { q: params.searchText }};
          }
          if (params.id) {
            // 返回動(dòng)態(tài)路由
            return '/with-params/:id';
          } else {
            // 返回路由地址
            return '/bar';
          }
        },
      },
    ];
    

別名

  • 別名:Vue Router 中,別名 指的是一個(gè)路由對(duì)應(yīng)兩個(gè)路徑,即訪問這兩個(gè)路徑都能跳轉(zhuǎn)到該路由。

比如對(duì)于下面的配置:

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

其實(shí)就是為/user_one設(shè)置了一個(gè)別名/user_1,此時(shí)訪問/user_one/user_1都可以導(dǎo)航到User.vue組件。

:如果想同時(shí)指定多個(gè)別名,則需進(jìn)行如下配置:

const routes = [
  {
    path: '/user_one', 
    component: User,
    alias: ['/user_1', '/user_yi'], // 使用數(shù)組即可
  },
];

路由組件傳參

  • 路由組件傳參:其實(shí)就是跳轉(zhuǎn)時(shí),給路由映射組件傳遞參數(shù)。

前面內(nèi)容,在模板中,我們都是通過(guò)$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'),
  },
];

這種做法其實(shí)緊耦合了路由與組件,對(duì)組件復(fù)用性產(chǎn)生消極影響。

一個(gè)更靈活的解決辦法是通過(guò)為組件動(dòng)態(tài)傳遞參數(shù),Vue Router 大致提供了如下幾種路由參數(shù)傳遞方法:

  • 布爾模式:為路由地址映射配置一個(gè)props: true,此時(shí)route.params會(huì)被傳遞給組件的props

    比如,上面的例子使用參數(shù)傳遞,可修改為如下:

    // file: router/index.js
    const routes = [
      {
        path: '/user/:name',
        component: () => import('@/components/User.vue'),
        props: true, // 使能路由參數(shù)傳遞
      },
    ];
    
    <!-- file: components/User.vue -->
    <template>
      <!-- 顯示傳遞的參數(shù) -->
      <h1>User Component: {{ name }}</h1>
    </template>
    
    <script>
    export default {
      name: 'User',
      props: {
        name: String, // 接收傳遞的參數(shù)
      },
    };
    </script>
    
  • 命名視圖:對(duì)于有命名視圖的路由,必須顯示為每個(gè)命名視圖定義props配置:

    const routes = [
      {
        path: '/user/:name',
        components: { default: User, sidebar: Sidebar },
        // default 視圖使能參數(shù)傳遞,sidebar 視圖關(guān)閉參數(shù)傳遞
        props: { default: true, sidebar: false }
      }
    ]
    
  • 對(duì)象模式:將props設(shè)置為對(duì)象,直接傳遞該對(duì)象給組件:

    const routes = [
      {
        path: '/user/:name',
        components: { default: User, sidebar: Sidebar },
        // 直接傳遞對(duì)象
        props: { name: 'Whyn' }
      }
    ]
    

    :對(duì)象模式此時(shí)直接傳遞props對(duì)象,與路由params沒有關(guān)系。當(dāng)我們需要傳遞靜態(tài)內(nèi)容給到路由組件時(shí),對(duì)象模式就很合適。

  • 函數(shù)模式:可以將props設(shè)置為一個(gè)函數(shù),該函數(shù)參數(shù)是路由地址信息$route,因此可以在函數(shù)內(nèi)部提取當(dāng)前路由相關(guān)信息,結(jié)合自己一些靜態(tài)內(nèi)容,組合進(jìn)行設(shè)置,更加靈活:

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

    上述配置中,比如此時(shí)我們跳轉(zhuǎn)到/user?name=Whyn時(shí),則會(huì)傳遞{ query: 'Whyn' }給到User.vue組件:

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

導(dǎo)航守衛(wèi)

  • 導(dǎo)航守衛(wèi):所謂 導(dǎo)航守衛(wèi),其實(shí)就是一些路由鉤子,在進(jìn)行路由跳轉(zhuǎn)或取消時(shí),會(huì)觸發(fā)相應(yīng)鉤子,我們可以在這些鉤子中,檢測(cè)路由跳轉(zhuǎn)及獲取相關(guān)數(shù)據(jù),植入我們自己的操作。

Vue Router 總共提供了如下三種類型導(dǎo)航守衛(wèi):

  • 全局守衛(wèi):可以監(jiān)控所有路由跳轉(zhuǎn)的導(dǎo)航守衛(wèi)。具體可在細(xì)分為如下三種類型:

    1. 全局前置守衛(wèi):當(dāng)觸發(fā)一個(gè)導(dǎo)航跳轉(zhuǎn)時(shí),全局前置守衛(wèi)會(huì)被觸發(fā)調(diào)用(多個(gè)全局前置守衛(wèi)會(huì)按照創(chuàng)建順序依次回調(diào))。

      可通過(guò)Router#beforeEach注冊(cè)一個(gè)全局前置守衛(wèi):

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

      全局前置守衛(wèi)是異步解析執(zhí)行的,即回調(diào)函數(shù)與asyncPromise工作方式一樣:

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

      全局前置守衛(wèi)每個(gè)守衛(wèi)方法可接受三個(gè)參數(shù):

      • to:表示即將要進(jìn)入的路由目標(biāo)
      • from:當(dāng)前導(dǎo)航正要離開的路由對(duì)象
      • next:可選參數(shù),用于觸發(fā)并傳遞額外數(shù)據(jù)給下一個(gè)路由對(duì)象:
      router.beforeEach((to, from, next) => {
          // 進(jìn)行管道的下一個(gè)鉤子
          next(); 
      
          // 中斷當(dāng)前導(dǎo)航,URL 重置為 from 路由路徑
          next(false);
      
          // 跳轉(zhuǎn)到指定路由 /home
          next('/home');
      
          // 同 next('/home')
          next( {path: '/home'} );
      
          // 如果 next 參數(shù)是一個(gè) Error 實(shí)例,則導(dǎo)航會(huì)被終止,
          // 且參數(shù) error 會(huì)被傳遞給 Router#onError() 回調(diào)
          next(error);
      })
      

      全局前置守衛(wèi)每個(gè)守衛(wèi)方法返回值有如下幾種可選:

      • true|undefined:返回trueundefined,表示導(dǎo)航有效,并自動(dòng)調(diào)用下一個(gè)導(dǎo)航守衛(wèi):

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

        :如果顯示聲明了第三個(gè)參數(shù)next,則必須手動(dòng)調(diào)用next進(jìn)行跳轉(zhuǎn):

        router.beforeEach((to, from, next) => {
            // false
            next(true);
            // 或者 undefined
            next();
        });
        
      • false:表示取消導(dǎo)航跳轉(zhuǎn),即當(dāng)前 URL 重置到from路由地址

        router.beforeEach((to, from) => {
            // 取消導(dǎo)航,停留在 from 路由頁(yè)面
            return false;
        });
        
      • 一個(gè)路由地址:指定導(dǎo)航跳轉(zhuǎn)地址,相當(dāng)于調(diào)用了Router#push

        const routes = [
          // ...
          { path: '/user', component: () => import('@/components/User.vue') },
        ],
        
        router.beforeEach((to, from) => {
          if (to.path === '/user') {
              return true;
            return '/user';
        });
        
      • Error:拋出異常,此時(shí)會(huì)取消導(dǎo)航并回調(diào)Router#onError全局錯(cuò)誤鉤子:

        router.beforeEach((to, from, next) => {
          // 手動(dòng)拋異常,會(huì)被 onError 捕獲到
          throw 'error occured!!';
        });
        
        router.onError((error, to, from) => {
          console.log(error, to, from);
        });
        
    2. 全局解析守衛(wèi):全局解析守衛(wèi)與全局前置守衛(wèi)類似,都是在 每次導(dǎo)航 時(shí)都會(huì)被觸發(fā),但是全局解析守衛(wèi)會(huì)確保在 所有組件內(nèi)守衛(wèi)和異步路由組件被解析后,才會(huì)被正確調(diào)用。

      可通過(guò)Router#beforeResolve方法注冊(cè)一個(gè)全局解析守衛(wèi):

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

      Router#beforeResolve是獲取數(shù)據(jù)或執(zhí)行任何其他操作(如果用戶無(wú)法進(jìn)入頁(yè)面時(shí)你希望避免執(zhí)行的操作)的理想位置。

    3. 全局后置鉤子:全局后置鉤子是在導(dǎo)航被確認(rèn)后,才進(jìn)行調(diào)用,因此,該鉤子不會(huì)攜帶next函數(shù),也不會(huì)改變導(dǎo)航狀態(tài)。

      可通過(guò)Router#afterEach注冊(cè)一個(gè)全局后置鉤子:

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

      Router#afterEach對(duì)于分析、更改頁(yè)面標(biāo)題、聲明頁(yè)面等輔助功能以及許多其他事情都很有用。

  • 路由獨(dú)享守衛(wèi):如果只關(guān)注特定路由導(dǎo)航跳轉(zhuǎn)事件,那么只需為相應(yīng)路由添加導(dǎo)航守衛(wèi)即可。

    只需在路由配置上定義beforeEnter函數(shù)即可注冊(cè)一個(gè)路由獨(dú)享導(dǎo)航守衛(wèi):

    const routes = [
      {
        path: '/home',
        component: () => import('@/components/Home.vue'),
        // 路由獨(dú)享守衛(wèi)
        beforeEnter: (to, from, next) => {
          console.log('Home: Route beforeEnter');
          next();
        },
        // 支持傳遞多個(gè)鉤子函數(shù)
        // beforeEnter: [(..)=>{..}, (..)=>{..}],
      },
    ];
    

    beforeEnter只在進(jìn)入路由時(shí)觸發(fā),更改params、queryhash時(shí),不會(huì)觸發(fā)beforeEnter。

  • 組件內(nèi)守衛(wèi):一個(gè)路由最終映射為一個(gè)組件,因此我們也可以在組件內(nèi)定義導(dǎo)航守衛(wèi),捕獲路由跳轉(zhuǎn)導(dǎo)航相關(guān)信息。

    組件內(nèi)守衛(wèi)在 Options API 中可通過(guò)為組件添加beforeRouteEnter、beforeRouteUpdatebeforeRouteLeave函數(shù)進(jìn)行注冊(cè):

    const UserDetails = {
      template: `...`,
      beforeRouteEnter(to, from) {
        // 在渲染該組件的對(duì)應(yīng)路由被驗(yàn)證前調(diào)用
        // 不能獲取組件實(shí)例 `this` !
        // 因?yàn)楫?dāng)守衛(wèi)執(zhí)行時(shí),組件實(shí)例還沒被創(chuàng)建!
      },
      beforeRouteUpdate(to, from) {
        // 在當(dāng)前路由改變,但是該組件被復(fù)用時(shí)調(diào)用
        // 舉例來(lái)說(shuō),對(duì)于一個(gè)帶有動(dòng)態(tài)參數(shù)的路徑 `/users/:id`,在 `/users/1` 和 `/users/2` 之間跳轉(zhuǎn)的時(shí)候,
        // 由于會(huì)渲染同樣的 `UserDetails` 組件,因此組件實(shí)例會(huì)被復(fù)用。而這個(gè)鉤子就會(huì)在這個(gè)情況下被調(diào)用。
        // 因?yàn)樵谶@種情況發(fā)生的時(shí)候,組件已經(jīng)掛載好了,導(dǎo)航守衛(wèi)可以訪問組件實(shí)例 `this`
      },
      beforeRouteLeave(to, from) {
        // 在導(dǎo)航離開渲染該組件的對(duì)應(yīng)路由時(shí)調(diào)用
        // 與 `beforeRouteUpdate` 一樣,它可以訪問組件實(shí)例 `this`
      },
    }
    

    組件內(nèi)守衛(wèi)在 Composition API 中可通過(guò)在setup函數(shù)中定義onBeforeRouteUpdateonBeforeRouteLeave分別注冊(cè) update 和 leave 守衛(wèi):

    <script>
    import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router';
    export default {
      name: 'Home',
    
      setup(props, context) {
        console.log('setup = onBeforeRouteEnter');
        // update 守衛(wèi)確認(rèn)導(dǎo)航跳轉(zhuǎn),因此沒有 next 參數(shù)
        onBeforeRouteUpdate((fto, from) => {
          console.log('onBeforeRouteUpdate');
        });
    
        // leave 守衛(wèi)同樣沒有 next 參數(shù)
        onBeforeRouteLeave((to, from) => {
          console.log('onBeforeRouteLeave');
        });
      },
    };
    </script>
    

    Composition API 中,setup等于beforeRouteEnter,在路由進(jìn)入時(shí)被觸發(fā);onBeforeRouteUpdate在第一次路由進(jìn)入時(shí),不會(huì)被觸發(fā),只有在該路由重復(fù)進(jìn)入,組件被復(fù)用時(shí)才會(huì)被觸發(fā)(比如params的改變);onBeforeRouteLeave在離開當(dāng)前路由時(shí)會(huì)被觸發(fā)。

最后,全局導(dǎo)航守衛(wèi)、路由獨(dú)享守衛(wèi)和組件內(nèi)守衛(wèi)完整的導(dǎo)航解析流程如下圖所示:

:流程圖來(lái)源于網(wǎng)上,侵刪。

路由導(dǎo)航完整解析流程

附錄

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Technology vue 3 安裝 npm install @vue/cli vue-router 4 安裝 ...
    行者深藍(lán)閱讀 1,485評(píng)論 0 0
  • 1. 后端路由階段 早期的網(wǎng)站開發(fā)整個(gè)HTML頁(yè)面是由服務(wù)器來(lái)渲染的。服務(wù)器直接生產(chǎn)渲染好對(duì)應(yīng)的HTML頁(yè)面, 返...
    itlu閱讀 591評(píng)論 0 3
  • 一、vue-router實(shí)現(xiàn)原理 SPA(single page application):單一頁(yè)面應(yīng)用程序,只有...
    walycode閱讀 1,132評(píng)論 1 3
  • 怎么重定向頁(yè)面? 第一種方法: 第二種方法:const router = new VueRouter({route...
    chendalei閱讀 1,409評(píng)論 0 0
  • 學(xué)習(xí)目的 學(xué)習(xí)Vue的必備技能,必須 熟練使用 Vue-router,能夠在實(shí)際項(xiàng)目中運(yùn)用。 Vue-rout...
    _1633_閱讀 92,939評(píng)論 3 58

友情鏈接更多精彩內(nèi)容