[Vue學習筆記] Vue router 4.x 常見及基本用法(1)

最近自己在練習的項目中有使用到 vue router 4.x,簡單做個筆記紀錄一下使用的方式,方便以後如果忘記再回頭過來複習。

統一管理路由

一般來說路由會統一集中管理,比如說在 src 底下新增一個 rotuer 文件夾,裡面會有 routes.jsindex.js,分別用於配置路由和路由守衛,路由守衛最常見的用處就是登入後才能進入部分頁面。

我的練習項目結構大概長這樣:

image 7 [Vue學習筆記] Vue router 4.x 常見及基本用法(1)

router/routes.js 的內容:

image 8 [Vue學習筆記] Vue router 4.x 常見及基本用法(1)

至於 rotuer/index.js 就是用於路由守衛(Navigation Guards),還有滾動行為…等:

image 9 [Vue學習筆記] Vue router 4.x 常見及基本用法(1)

滾動行為

可能看到滾動行為並不知道這是什麼東西,但是試想一個例子,今天你在逛蝦皮,你搜索了一個關鍵字,你在最後一排找到了你好奇的商品然後點了進去,此時新開啟的商品頁面會滾動到頂部,好方便你從最上方開始慢慢瀏覽商品的訊息。

但是當你沒有特別去設定滾動行為的時候,你會發現你打開商品頁面,滾動條會保持在你打開這個頁面之前的高度。

始終滾動到頂部

vue router 有提供一個 scrollBehavior 方法,你可以在 createRouter 中加上 scrollBehavior 來設定希望切換到新路由時,要滾動到哪個位置:

import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes";

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    // 當切換到新路由時,始終滾動到頂部
    return { top: 0 };
  },
});

滾動到某元素

你也可以通過 el 傳遞一個 CSS 選擇器或一個 DOM 元素。在這種情況下,top 和 left 將被視為該元素的相對偏移量。但要注意如果返回一個假值,或者是一個空物件,那麼不會發生滾動。

scrollBehavior(to, from, savedPosition) {
    // 始終在元素 #main 上方滾動 10px
    return {
      el: '#main', // document.getElementById('main')
      top: -10,
    }
},

延遲滾動

如果需要在滾動前等待,可以用 promise 搭配定時器來達到效果:

scrollBehavior(to, from, savedPosition) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ left: 0, top: 0 })
      }, 500)
    })
},

導航守衛

大部分的項目中都有需要登入後才能訪問的頁面,我們可以利用導航守衛,在路由跳轉之前先檢查訪客是登入,再選擇是要繼續訪問或者跳轉至登入頁面。路由守衛還可以再細分為全域的,單個路由獨享的,或者組件級的。

全域守衛

首先需要先使用 createRouter 新增一個路由實例,然後使用 beforeEach 註冊一個全域路由守衛:

守衛方法可以接收兩個參數,to 即為要進入的目標from當前導航正要離開的路由。實際上還有一個可選參數 next,不過 next 沒有寫好會重複調用,造成沒必要的跳轉,所以使用的話要非常仔細。

import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes";

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    return { top: 0 };
  },
});

// 全域守衛: 前置守衛 (在路由跳轉之間進行判斷)
router.beforeEach(async (to, from, next) => {
  // ...
});

舉一個最基本的例子,完成以下邏輯處理:

  • 已登入
    1. 無法再訪問登入頁註冊頁,如果訪問須直接返回首頁。
    2. 若訪問的是其他頁面,則重新獲取用戶信息,若獲取成功代表 token 有效,繼續跳轉;若失敗即 token 失效,需登出並回到登入頁
  • 未登入
    1. 當欲訪問的頁面需要登入才能訪問,則在跳轉至登入頁之前先將欲訪問的路由存放在 query 中,並在登入後重新返回該路由
    2. 訪客可以直接訪問的頁面直接放行。
const privateRoutes = ["Trade", "Center", "Pay"];

router.beforeEach(async (to, from, next) => {
  let token = store.state.user.token;
  let name = store.state.user.userInfo.name;

  // 已登入
  if (token) {
    // 已經登入便無法再訪問登入頁及註冊頁, 直接返回首頁
    if (to.name === "Login" || to.name === "Register") {
      next('/home');
    } else {
    try {
        // 獲取用戶信息
        await store.dispatch("getUserInfo");
        next();
      } catch (error) {
        // token 失效則登出, 並返回登入頁
        await store.dispatch("userLogout");
        next("/login");
      }
  }
  } else {  // 未登入
    let toName = to.name;
    // 訪問的頁面需要登入
    if (privateRoutes.some((name) => toName.indexOf(name) !== -1)) {
      // 用 query 紀錄原來要去的路由
      next("/login?redirect=" + toName);
    } else {
      // 訪客可以直接訪問的頁面就直接放行
      next();
    }
  }
});

登入後跳轉至未登入前欲訪問的路由還需要修改登入方法:

// 用戶登入
const login = async () => {
  try {
     const { phone, password } = data;
     if (phone && password) {
       await store.dispatch("userLogin", { phone, password });
       // 獲取 query: redirect
       // 如果 redirect 有值則跳轉至該路由, 如果沒有則直接跳轉至首頁
       let toPath = route.query.redirect || "/home";
     router.push(toPath);
     }
  } catch (error) {
     alert(error.message);
  }
};

路由獨享守衛

有些情況只針對某幾個路由,也可以使用 beforeEnter 來設定該路由獨享守衛:

const routes = [
  {
    path: "/trade",
    name: "Trade",
    component: Trade,
    meta: { showFooter: true },
    // 路由獨享守衛
    beforeEnter: (to, from, next) => {
      if (from.path == "/shopcart") { // 只有從 shopcart 跳轉至該路由才放行
        next();
      } else { // 否則返回跳轉前的頁面
        next(false);
      }
    },
  },
]

beforeEnter 只在進入路由時觸發,不會在 params、query 或 hash 改變時觸發。例如,從 /users/2 進入到 /users/3 或者從 /users/2#info 進入到 /users/2#projects 時才會觸發。

路由組件懶加載

當打包構建應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的程式碼塊,然後當路由被訪問的時候才加載對應組件,這樣就會更加高效:

// 替換前
import UserDetails from './views/UserDetails'
const router = createRouter({
  // ...
  routes: [
    {
      path: '/users/:id',
      component: UserDetails
    }
  ],
});
// 替換後
const router = createRouter({
  // ...
  routes: [
    {
      path: '/users/:id',
      component: () => import('./views/UserDetails') 
    }
  ],
})
0 0 評分數
Article Rating
訂閱
通知
guest
0 Comments
在線反饋
查看所有評論