今天會延續昨天尚未看完的一些常見用法,繼續從vue-router學react-router。在正式開始之前,也稍微前情提要一下,昨天我們除了了解什麼是react-router及react-router-dom外,也看了「路由定義及嵌套路由」、「處理匹配不到的route頁面以及重新導向」、「路由相關的Lazy Loading設定」的部分,今天會繼續看更多的常見用法!
如果想要在頁面中設定超連結,第一個想到的都是a標籤,但是由於使用a標籤會觸發頁面重整,而我們會使用SPA的架構,也就是想避免網頁一直重整所造成不佳的使用者體驗,所以當我們使用SPA結合路由設定時,會使用vue-router和react-router-dom所提供的用法來取代一般的a標籤・
vue-router
vue-router用來取代a標籤的用法是router-link
,再透過to屬性
把要前往的path設定上去,也可以使用設定路由時,有設定的路由name來設定to屬性,也可以在帶上params。
<template>
<div>
<p>Main page</p>
<router-link to="/page-a">Go to PageA</router-link>
<router-link :to="{ name: 'PageB'}">Go to PageB</router-link>
</div>
</template>
react-router
react-router用來取代a標籤的用法是Link
,一樣是透過to屬性
把要前往的path帶上去。雖然react-router沒有router name的設定,所以沒辦法透過router name來設定to屬性,但是可以用pathname進行設定。
import { Link } from 'react-router-dom';
export default function MainPage() {
return (
<>
<div>MainPage</div>
<Link to="/page-a">Go to PageA</Link>
<Link to={{ pathname: '/page-b' }}>Go to PageB</Link>
</>
)
}
在實作navigation的時候,會需要知道現在的路由是否有匹配到navigation上的超連結,再透過這個判斷來呈現UI的顯示,例如:讓目前點選到的navigation 超連結可以用不同的顏色顯示。雖然說可以自己寫一個state來做記錄,但是vue-router本身就已經有提供一個方法可以讓我們快速判斷,這裡也來看看react-router的話,可以怎麼做。
vue-router
當使用router-link時,如果路由與超連結完全匹配到,就會多加一個叫做 「router-link-exact-active
」的class,如果只有部分匹配到的話,則是多一個「router-link-active
」class。
<template>
<div>
<h1>Parent Layout</h1>
<router-link to="/parent">Main</router-link>
<router-link :to="{ name: 'ChildA' }">Go to Child A</router-link>
<router-link :to="{ name: 'ChildB' }">Go to Child B</router-link>
<br>
<RouterView />
</div>
</template>
了解這個特點後,需要時,就可以利用router-link-exact-active這個class name來設定樣式,讓匹配到路由的時候,router-link會顯示指定的顏色。
react-router
react-router的話,可以透過NavLink
搭配end
prop的使用來達到前面提到的vue-router當超連結和路由設定匹配時,className看得到標示的效果。
如果沒有加上end prop的話,只要前段有匹配到,就會被加上active的class name,也就是說Main的那個超連結點下去,也會被加上active。加上end的話,則需要頭尾都完全匹配才會被加上active的class name。
import { Outlet, NavLink } from 'react-router-dom';
export default function Layout() {
return (
<div>
<h1>Parent</h1>
<NavLink to="/parent" end>Main</NavLink>
<NavLink to="/parent/child-a" end>Go to Child A</NavLink>
<NavLink to="/parent/child-b" end>Go to Child B</NavLink>
<br/>
<Outlet />
</div>
)
}
這樣設定完之後,就透過acitve這個class name來加上樣式。
有時候我們會透過router的一些方法來進行歷史的頁面的返回和往下一頁的操作,這樣也就不需要寫死接下來要往哪個頁面移動,讓頁面移動的操作更能符合各種情境。
vue-router
vue-router可以使用useRouter
這個hook來進行go、back、push的路由操作。
<script setup>
import { RouterView, useRouter } from 'vue-router';
const router = useRouter();
const go = () => {
// 歷史頁面的往前操作
router.go(1);
};
const back = () => {
// 歷史頁面的返回操作
router.back();
};
const push = () => {
// 指定前往特定頁面
router.push('/');
};
</script>
react-router
react-router則是使用useNavigate
來進行類似的操作。
import { Outlet, NavLink, useNavigate } from 'react-router-dom';
export default function Layout() {
const navigate = useNavigate();
const go = () => {
// 歷史頁面的往前操作
navigate(1);
};
const back = () => {
// 歷史頁面的返回操作
navigate(-1);
};
const push = () => {
// 指定前往特定頁面
navigate('/');
};
// 略...
}
如果需要取得路由的資料或是用到的參數,也有對應的用法可以使用。
vue-router
vue-router可以透過useRoute
取得到路由的資訊,包含整個完整的path、params、name等。
<script setup>
import { RouterView, useRouter, useRoute } from 'vue-router';
const route = useRoute();
console.log(route)
</script>
react-router
react-router是透過useLocation
來取得路由的基本資訊,想取得params和query的話,則是透過useParams和useSearchParams。
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
export default function Layout() {
// 取得一些基本的路由資訊
const location = useLocation();
// 取得params
const params = useParams();
// searchParams可以取得query, setSearchParams可以更新query
const [searchParams, setSearchParams] = useSearchParams();
// 取得query
console.log('query: ', searchParams.get('id'));
// 略...
}
我們在寫路由相關的設定時,除了定義什麼路由要對到什麼頁面外,有時候還會需要依照有沒有權限來去設定這個人能不能點進這個路由,或是在進入頁面前進行一些操作。
vue-router
如果是寫Vue的話,通常都可以使用vue-router所提供的beforeEach或BeforeEnter等的方法來處理相關的邏輯。
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
// 略...
});
// to是將要進入哪個路由,from是從哪個路由進來,next則用來表示進入下一個callback
router.beforeEach(async (to, from, next) => {
// 在即將要進入某些路由前,先確認是否有權限,再決定要怎麼處理這個進入路由的動作
});
react-router
但在react-router-dom的用法中,卻沒有這樣的方法可以使用,所以只能用其他的方法搭配router來呈現這樣的效果。在這個情境中,可能可以會想到那就用一個函式去做權限的檢查吧!例如以下的寫法:
import { createHashRouter, Navigate } from "react-router-dom";
import { lazy, Suspense } from 'react';
import Home from '../Home';
const Profile = lazy(() => import('../Profile'));
const Intro = lazy(() => import('../Intro'));
const hasPermission = false;
// 做一個當作guard的函式
const checkPermission = (component) => {
if (hasPermission) {
return (
<Suspense fallback={<div>Loading...</div>}>
{component}
</Suspense>
)
}
return <Navigate to="/" />;
};
const Router = createHashRouter([
{
path: '/',
element: <Home />
},
{
path: 'profile',
// 使用在需要檢查的路由上
element: checkPermission(<Profile />)
},
{
path: 'intro',
element: (
<Suspense>
<Intro />
</Suspense>
)
},
]);
export default Router;
但是上述的寫法可能會出現一些問題,導致因為權限有無的頁面轉導的時候,可能發生一些無法確實確認權限,而出現的無限迴圈。主要原因是因為上述寫法會在初始router設定時,就先執行了checkPermission的函式,使得真正需要確認有無權限時,實際上卻沒有進行確認。
這個時候的調整方式是將「確認權限的邏輯包在一個元件裡面,並將它放在router設定中的父層,實際要渲染的頁面內容則是放在children
」,也就是前面有提到的嵌套是路由的寫法。
拿來判斷權限的元件
import { Outlet, Navigate } from 'react-router-dom';
const PrivateRouter = () => {
const localStorageToken = localStorage.getItem('token');
return {localStorageToken ? <Outlet /> : <Navigate to="/login" />};
};
export default PrivateRouter;
路由設定則改寫成以下這樣
const Router = createHashRouter([
{
path: `/`,
element: <PrivateRouter />,
children: [
{
path: 'profile',
element: <Profile />
},
],
},
]);
export default Router;
這樣改寫後,也就能在實際渲染時,才去值行檢查權限的邏輯。
到目前為止已經透過盤點vue-router的一些常見用法,把react-router-dom的用法也看過一遍了!雖然其實關於router,不論是vue-router或是react-router-dom其實都還有更多能夠使用的方法,但因為自己在這之前並沒有好好認識及使用react-router-dom,所以就是先專注在這些比較基本,在開發專案中,也最常會使用到的一些用法。