React Router v6 完全指南
大家好,我是 CUGGZ。
React Router 是 React 生態系統中最受歡迎的第三方庫之一,近一半的 React 項目中使用了 React Router,下面就來看看如何在 React 項目中使用 React Router v6 吧!
- 概述
React Router 創建於 2014 年,是一個用於 React 的聲明式、基於組件的客戶端和服務端路由庫,它可以保持 UI 與 URL 同步,擁有簡單的 API 與強大的功能。
大多數現代 React 項目使用 npm、yarn、pnpm 等包管理器來管理項目依賴項。要將 React Router 添加到現有項目,就需要根據使用的包管理器來安裝依賴:
// npm
npm install react-router-dom@6
// pnpm
pnpm add react-router-dom@6
// yarn
yarn add react-router-dom@6
接下來,使用 CodeSandBox 來創建一個 React + TypeScript 項目,使用核心庫的版本如下:
-
react
:18.0.0 -
react-dom
:18.0.0 -
react-router
:6.3.0 -
react-router-dom
:6.3.0
Demo 初始目錄結構如下:
- public
- src
- App.tsx
- index.tsx
- style.css
- package.json
- tsconfig.json
在介紹 React Router 的概念以前,需要先區分兩個概念:
-
react-router
:爲 React 應用提供了路由的核心功能; -
react-router-dom
:基於 react-router,加入了在瀏覽器運行環境下的一些功能。
- 基本使用
(1)BrowserRouter
要想在 React 應用中使用 React Router,就需要在 React 項目的根文件(index.tsx
)中導入 Router 組件:
import { StrictMode } from "react";
import * as ReactDOMClient from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
const rootElement = document.getElementById("root");
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);
在這個文件中,我們導入了 BrowserRouter
組件,然後使用該組件包裹了 App
組件。現在,在這個 BrowserRouter
組件中,來自 react-router-dom
的其他組件和 hooks 就可以正常工作了。
BrowserRouter
是最常用的路由方式,即瀏覽器路由。官方文檔也建議將 BrowserRouter
組件用於 Web 應用程序。除了這種方式,React Router 還支持其他幾種路由方式:
-
HashRouter
:在路徑前加入#
成爲一個哈希值,Hash 模式的好處是不會因爲刷新頁面而找不到對應路徑; -
MemoryRouter
:不存儲 history,路由過程保存在內存中,適用於 React Native 這種非瀏覽器環境; -
NativeRouter
:配合 React Native 使用,多用於移動端; -
StaticRouter
:主要用於服務端渲染時。
(2)NavLink
在創建導航鏈接之前,先在App.tsx
組件中創建一個標題:
import "./styles.css";
export default function App() {
return (
<div class>
<header>
<h1>Hello World</h1>
</header>
</div>
);
}
頁面如下:
下面來創建三個導航鏈接,這些鏈接會指向App.tsx
中的一些路由。這時就需要導入 NavLink
組件,它是一個導航鏈接組件,類似於 HTML 中的<a>
標籤。NavLink
組件使用 to
來指定需要跳轉的鏈接:
import { NavLink } from "react-router-dom";
import "./styles.css";
export default function App() {
return (
<div class>
<header>
<h1>Hello World</h1>
</header>
<nav>
<NavLink to="">首頁</NavLink>
<NavLink to="product">產品</NavLink>
<NavLink to="about">關於</NavLink>
</nav>
</div>
);
}
當點擊 “首頁” 時,就會跳轉至路由 /
:
當點擊 “產品” 時,就會跳轉至路由 /product
:
當點擊 “關於” 時,就會跳轉至路由 /about
:
可以看到,當點擊這些導航鏈接時,網頁的 URL 就會改變,跳轉到對應的路由。
NavLink
是存在 active
狀態的,所以可以爲active
狀態和非active
狀態的導航鏈接添加樣式:
.nav-active {
color: red;
font-weight: bold;
}
接下來爲導航鏈接添加樣式判斷條件,選擇性的爲其添加nav-active
類:
import { NavLink } from "react-router-dom";
import "./styles.css";
export default function App() {
return (
<div class>
<header>
<h1>Hello World</h1>
</header>
<nav>
<NavLink
to=""
className={({ isActive }) => isActive ? "nav-active" : void 0}
>
首頁
</NavLink>
<NavLink to="product">產品</NavLink>
<NavLink to="about">關於</NavLink>
</nav>
</div>
);
}
當點擊 “首頁” 時,會跳轉到首頁,導航鏈接會變成 active
狀態。這時,“首頁” 二字會變成紅色,字體也會加粗:
(3)Link
在 react-router-dom
中,可以使用 Link
組件來創建常規鏈接。Link
組件與 NavLink
組件非常相似,唯一的區別就是 NavLink
存在 active
狀態,而 Link
沒有。
Link
組件和 NavLink
組件的使用方式類似,例如在產品頁面有一個返回首頁的按鈕,需要傳遞給 to
需要跳轉的路徑:
import { Link } from "react-router-dom";
import "./styles.css";
export default function Product() {
return (
<div class>
<header>
<Link to="/">返回首頁</Link>
</header>
</div>
);
}
如果需要對 Link
進行更多控制,也可以傳遞給 to
一個對象,在這個對象中,可以通過 search 屬性來添加查詢字符串或通過 hash
屬性來傳遞 hash
值,例如:
import { Link } from "react-router-dom";
import "./styles.css";
export default function Settings() {
return (
<div class>
<header>
<h1>Hello World</h1>
<Link
to={{
pathname: "/settings",
search: "?sort=date",
hash: "#hash"
}}
>
設置
</Link>
</header>
</div>
);
}
點擊 “設置” 時,路由就變成了:/settings?sort=date#hash
(4)Routes
下面來看看如何將路由映射爲對應的頁面(組件)。首先需要從 react-router-dom 中導入一個名爲 Routes
的組件,它將包含可以在頁面特定部分顯示的所有不同的路由。
在 index.tsx
中進行如下修改:
import { NavLink, Routes, Route } from "react-router-dom";
import Product from "./Product";
import "./styles.css";
export default function App() {
return (
<div class>
<header>
<h1>Hello World</h1>
</header>
<nav>
<NavLink to="">首頁</NavLink>
<NavLink to="product">產品</NavLink>
<NavLink to="about">關於</NavLink>
</nav>
<Routes>
</Routes>
</div>
);
}
我們需要在 Routes
組件中使用 Route
組件來定義所有路由。該組件接受兩個 props
:
-
path
:頁面 URL 應導航到的路徑,類似於 NavLink 組件的 to; -
element
:頁面導航到該路由時加載的元素。
Route
組件用於將應用的位置映射到不同的 React 組件。例如,當用戶導航到 /product
路徑時呈現 Product
組件,可以這樣來寫:
import { NavLink, Routes, Route } from "react-router-dom";
import Product from "./Product";
import About from "./About";
import Home from "./Home";
import Error from "./Error";
import "./styles.css";
export default function App() {
return (
<div class>
<header>
<h1>Hello World</h1>
</header>
<nav>
<NavLink to="">首頁</NavLink>
<NavLink to="product">產品</NavLink>
<NavLink to="about">關於</NavLink>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/product" element={<Product />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<Error />} />
</Routes>
</div>
);
}
當點擊 “產品” 時,就會加載 Product
組件:
當點擊 “首頁” 時,就會加載 Home
組件:
當在地址欄輸入一個沒有定義的路由時,就會加載 Error
組件:
從上面的代碼中可以看到,如果想要在所有 Route
都不匹配時就渲染 404 頁面,只需將 404 頁面對應的 Route
的 path
設置爲 *
:
<Routes>
<Route path="/" element={<Home />} />
<Route path="product" element={<Product />} />
<Route path="about" element={<About />} />
<Route path="*" element={<Error />} />
</Routes>
(5)路由順序
在 React Router v6 以前,我們必須按照一定的順序來定義路由,以獲得準確的渲染。在 v6 及之後的版本中,路由定義的順序無關緊要。
以下代碼在 v5 中,/product/new
將匹配到第一個路由並渲染 Product
組件:
<Switch>
<Route path="/product/:id" component={Product} />
<Route path="/product/new" component={NewProduct} />
</Switch>
而在 v6 中,將 <Switch>
組件替換爲了 <Routes>
組件。/products/new
將匹配這兩個路由,但只會渲染NewProduct
組件,因爲它是更具體的匹配:
<Routes>
<Route path="/product/:id" element={<Product />} />
<Route path="/product/new" element={<NewProduct />} />
</Routes>
- 編程式導航
React Router 提供了兩種不同的編程式導航方式:
-
聲明式導航組件:
<Navigate>
組件 -
命令式導航方法:
useNavigate
Hook
我們可以使用這兩種編程的方式來跳轉到指定的路由,也可以實現路由的重定向,比如在瀏覽器的地址欄輸入一個 URL 並進行跳轉時,如果應用中沒有定義該路由,就跳轉到應用的首頁。
(1)Navigate
<Navigate>
組件是一種聲明式的導航方式。使用 Navigate
組件時,首先需要從 react-router-dom 導入 Navigate
組件。然後在 Navigate
組件中通過 to
props 來指定要跳轉的路徑:
import { NavLink, Routes, Route, Navigate } from "react-router-dom";
import Product from "./Product";
import About from "./About";
import Home from "./Home";
import "./styles.css";
export default function App() {
return (
<div class>
<header>
<h1>Hello World</h1>
</header>
<nav>
<NavLink to="">首頁</NavLink>
<NavLink to="product">產品</NavLink>
<NavLink to="about">關於</NavLink>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="product" element={<Product />} />
<Route path="about" element={<About />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</div>
);
}
這樣,當在瀏覽器地址欄輸入一個未定義的路由時,就會要轉到首頁。
(2)useNavigate
useNavigate
Hook 是一種命令式導航方式。使用這個 Hook 時,首先需要從 react-router-dom 中導入 useNavigate
,然後傳遞給它需要跳轉的路由即可。假如在提交完表單之後,跳轉到主頁,可以這樣實現:
import { useNavigate } from 'react-router-dom
function Register () {
const navigate = useNavigate()
return (
<div>
<Form afterSubmit={() => navigate('/')} />
</div>
)
}
- 通過路由傳遞狀態
在 react-router-dom 中可以通過以下三種方式來傳遞狀態:
-
使用
Link
組件 -
使用
Navigate
組件 -
使用
useNavigate
鉤子
(1)Link
下面來使用 Link 組件通過 state
props 來將數據從產品頁面傳遞到主頁:
import React from "react";
import { Link } from "react-router-dom";
function Contact() {
return (
<div>
<header>產品頁面</header>
<Link to="/" state={"From Product"}>
返回
</Link>
</div>
);
}
export default Contact;
現在我們就將需要的數據傳遞出來了,那該如何在首頁獲取從產品頁面傳遞出來的數據呢?可以在接收信息的頁面(首頁)中使用一個名爲 useLocation
的鉤子來獲取數據:
import { useLocation } from "react-router-dom";
import "./styles.css";
export default function Settings() {
let location = useLocation();
return (
<div class>
<header>首頁</header>
<p>{location.state}</p>
</div>
);
}
當在產品頁面點擊返回時,就會跳轉到首頁,並且首頁顯示了從產品頁面傳遞出來的數據:
(2)Navigate
Navigate
組件也可以在 react-router-dom 中傳遞狀態,其使用方式和 Link
組件類似。假如當點擊關於按鈕時,跳轉到首頁,並告訴首頁該跳轉是從哪個頁面來的:
<Route path="/about" element={<Navigate to="/" state={"From About"} />} />
在首頁中仍然是使用 useLocation
鉤子來獲取狀態值:
import { useLocation } from "react-router-dom";
import "./styles.css";
export default function Settings() {
let location = useLocation();
return (
<div class>
<header>首頁</header>
<p>{location.state}</p>
</div>
);
}
當在首頁點擊 “關於” 時,就會重定向到首頁,並且首頁中會顯示 From About:
(3)useNavigate
上面我們介紹瞭如何使用 useNavigate
鉤子來進行重定向,在調用 navigate()
函數時,給它傳遞了一個參數,即要重定向的路徑。實際上,navigate()
函數接受兩個參數,第一個參數就是跳轉的路徑,第二個參數是包含狀態的對象。可以藉助 useNavigate
Hook 來實現狀態傳遞:
import { useNavigate } from 'react-router-dom
function Register () {
const navigate = useNavigate()
return (
<div>
<Form afterSubmit={() => navigate('/', { state: "From the About Page"})} />
</div>
)
}
在首頁中仍然是使用 useLocation
鉤子來獲取狀態值,和上面兩種方式一樣,這裏不再多介紹。
- 動態路由
一個很常見的場景,在維基百科進行搜索時,URL 的模式始終是一樣的,如下:
https://zh.wikipedia.org/wiki/{keyword}
這裏的 keyword
就是我們在維基百科中搜索的內容,這個內容是不固定的,並且有很多很多,我們不可能爲每個關鍵詞都創建一個路由。其實,只需要聲明一個帶有 keyword
佔位符的路由即可。對於上面的例子,只需要將 Route
組件的 path
props 聲明爲這樣:
<Route path="/wiki/:keyword" element={<Wiki />} />
這時,無論是訪問/wiki/javascript
還是/wiki/react
,都會加載 Wiki
組件。
那我們該如何在組件中訪問 URL 中的動態部分呢?從 v5.1 開始,React Router 就提供了一個 useParams
Hook,它返回一個對象,該對象具有 URL 參數及其值之間的映射。使用方式如下:
import React from 'react'
import {useParams} from 'react-router';
function Wiki() {
const { keyword } = useParams()
return (
<div>{ keyword }</div>
)
}
這樣,通過獲取到的 URL 參數,就可以請求頁面對應的數據。
- 嵌套路由
嵌套路由允許父路由充當包裝器並控制子路由的渲染。比如,在應用中點擊消息時,會跳轉到 /messages
路由,並顯示所有的通知列表。當點擊某一條消息時,就會跳轉到 /messages/:id
路由,這時就能看到指定 id
的消息詳情,同時消息列表是顯示在左側的。這個場景就要依賴嵌套路由來實現。下面來看看如何使用 React Router 實現這種嵌套路由模式。
從最基礎的結構開始定義:
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/messages" element={<Messages />} />
<Route path="/settings" element={<Settings />} />
</Routes>
);
}
現在,我們希望 Messages
能夠控制渲染子路由,那能不能直接在 Messages
組件中來定義子路由呢?就像這樣:
function Messages() {
return (
<div>
<Conversations />
<Routes>
<Route path=":id" element={<MessagesDetails />} />
</Routes>
</div>
);
}
現在,當用戶導航到 /messages
時, React Router 會呈現 Messages
組件。Messages
組件中通過 Conversations
組件來顯示消息列表,然後使用將 /messages/:id
映射到 Chat
組件的 Route
來渲染另一個 Routes
。
注意: 這裏不必在嵌套路由中包含完整的
/messages/:id
路徑,因爲Routes
是很智能的,當省略了前導/
,就會認爲這條路徑是相對於父級/messages
的。
這樣,只有在跳轉到/Messages
時纔會渲染消息列表。當訪問與 /messages/:id
模式匹配的路由時,消息列表就消失了,嵌套路由永遠不被會渲染。
爲了解決這個問題,我們需要告訴 React Router 想要在路由爲 /messages
時或者爲任何其他匹配 /messages/*
模式的路由時要渲染消息列表。
那如果只是將路徑修改爲 /messages/*
會怎樣呢?
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/messages/*" element={<Messages />} />
<Route path="/settings" element={<Settings />} />
</Routes>
);
}
這樣做的確生效了,無論是導航到 /messages
還是 /messages/:id
,都能正常加載消息列表組件:
通過將 /*
附加到 /messages
路徑的末尾,實際上是在告訴 React Router,Messages
有一個嵌套的 Routes
組件。並且父路徑應該匹配 /messages
以及與 /messages/*
匹配的任何其他路由。
當我們希望在子 Route
控制渲染嵌套路由時,這是有效的。但是如果我們希望在 App 組件包含創建嵌套路由所需的所有信息,而不是必須在 Messages
組件中定義呢?React Router 也是支持這種創建嵌套路由的方式:
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/messages" element={<Messages />}>
<Route path=":id" element={<MessagesDetails />} />
</Route>
<Route path="/settings" element={<Settings />} />
</Routes>
);
}
這裏以聲明式的方式將子 Route
嵌套爲父 Route
的子級。和上面一樣,子路由是相對於父路由的,因此不需要包含父 (/messages
) 路徑。
現在,只需要告訴 React Router 應該在父路由(Messges
)中的哪個位置渲染子路由(MessagesDetails
)。這就就需要使用 React Router 的 Outlet
組件:
import { Outlet } from "react-router-dom";
function Messages() {
return (
<div>
<Conversations />
<Outlet />
</div>
);
}
如果應用的路由與嵌套 Route
的路徑匹配,Outlet
組件就會渲染 Route
的元素。根據上面的 Routes
,如果在當前的路由是 /messages
,Outlet
組件將渲染爲 null
;如果當前的路由是 /messages/1
,Outlet
組件將渲染 <MessagesDetails />
組件。
- 查詢參數
在 React Router 中,如何從 URL 中獲取參數呢?例如以下 URL:
twitter.com/search?q=react&src=typed_query&f=live
從 v6 開始,React Router 使用 URLSearchParams
API 來處理查詢字符串,URLSearchParams
內置於所有瀏覽器(IE 除外)中,並提供了處理查詢字符串的實用方法。當創建 URLSearchParams
實例時,需要向它傳遞一個查詢字符串:
const queryString = "?q=react&src=typed_query&f=live";
const sp = new URLSearchParams(queryString);
sp.has("q"); // true
sp.get("q"); // react
sp.getAll("src"); // ["typed_query"]
sp.get("nope"); // null
sp.append("sort", "ascending");
sp.toString(); // "?q=react&src=typed_query&f=live&sort=ascending"
sp.set("q", "bytes.dev");
sp.toString(); // "?q=bytes.dev&src=typed_query&f=live&sort=ascending"
sp.delete("sort");
sp.toString(); // "?q=bytes.dev&src=typed_query&f=live"
React Router 提供了一個自定義的 useSearchParams
Hook,它是基於 URLSearchParams
進行的封裝。useSearchParams
返回一個數組,該數組第一個元素是 URLSearchParams
的實例,第二個元素是更新查詢參數的一個方法。
對於上面的 URL,使用 useSearchParams
從查詢字符串中獲取值:
import { useSearchParams } from 'react-router-dom'
const Results = () => {
const [searchParams, setSearchParams] = useSearchParams();
const q = searchParams.get('q')
const src = searchParams.get('src')
const f = searchParams.get('f')
return (
// ...
)
}
如果需要更新查詢字符串,可以使用 setSearchParams
,向它傳遞一個對象,該對象的key/value
對將作爲 &key=value
添加到 url:
const Results = () => {
const [searchParams, setSearchParams] = useSearchParams();
const q = searchParams.get('q')
const src = searchParams.get('src')
const f = searchParams.get('f')
const updateOrder = (sort) => {
setSearchParams({ sort })
}
return (
...
)
}
- Route 配置
React Router v6 內置了一個 useRoutes
Hook,它在功能上等同於 <Routes>
,但它是使用 JavaScript 對象而不是 <Route>
元素來定義路由。這個對象具有與普通 <Route>
元素相同的屬性,但它們不需要使用 JSX 來編寫。
useRoutes
的返回值要麼是一個有效的 React 元素(可以使用它來渲染路由樹),如果沒有匹配項,則返回 null
。
假如應用中有以下路徑:
/
/invoices
:id
pending
complete
使用 <Route>
組件來定義路由將會是這樣的:
export default function App() {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/invoices" element={<Invoices />}>
<Route path=":id" element={<Invoice />} />
<Route path="pending" element={<Pending />} />
<Route path="complete" element={<Complete />} />
</Route>
</Routes>
</div>
);
}
而 useRoutes 是利用 JavaScript 對象完成的,而不是使用 React 元素 (JSX) 來聲明路由。定義形式如下:
import { useRoutes } from "react-router-dom";
const routes = useRoutes([
{ path: "/", element: <Home /> },
{
path: "/invoices",
element: <Invoices />,
children: [
{ path: ":id", element: <Invoice /> },
{ path: "/pending", element: <Pending /> },
{ path: "/complete", element: <Complete /> },
],
},
]);
export default function App() {
return (
<div>
<Navbar />
{routes}
</div>
);
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8JPj9Rz5xyrnAloeno0RNA