React Router 已经升级到了版本6, 与版本5有了挺大区别,让我们跟着官方教程来学习一下 React Router 6 的使用吧。

安装

使用 vite 创建一个项目

  1. yarn create vite router-tutorial --tempalte react

安装 React Router 6

  1. yarn add react-router-dom@6

编辑 App.jsx

  1. export default function App() {
  2. return (
  3. <div>
  4. <h1>Bookkeeper!</h1>
  5. </div>
  6. );
  7. }

编辑 main.jsx

  1. import { render } from "react-dom";
  2. import App from "./App";
  3. const rootElement = document.getElementById("root");
  4. render(<App />, rootElement);

连接URL

BrowserRouter 包裹住 App 组件, 应用就能与浏览器的 URL 连接起来了

  1. import { render } from "react-dom";
  2. import { BrowserRouter } from "react-router-dom";
  3. import App from "./App";
  4. const rootElement = document.getElementById("root");
  5. render(
  6. <BrowserRouter>
  7. <App />
  8. </BrowserRouter>,
  9. rootElement
  10. );

添加Links

Link 的作用是在不导致页面重新加载的情况下切换浏览器的URL

  1. import { Link } from "react-router-dom";
  2. export default function App() {
  3. return (
  4. <div>
  5. <h1>Bookkeeper!</h1>
  6. <nav style={{
  7. borderBottom: "solid 1px",
  8. paddingBottom: "1rem",
  9. }}>
  10. <Link to="/invoices">Invoices</Link>
  11. <Link to="/expenses">Expenses</Link>
  12. </nav>
  13. </div>
  14. );
  15. }

image.png

添加 Routes

创建2个新文件

  • src/routes/invoices.jsx
  • src/routes/expenses.jsx

内容如下

  1. export default function Expenses() {
  2. return (
  3. <main style={{ padding: "1rem 0" }}>
  4. <h2>Expenses</h2>
  5. </main>
  6. );
  7. }
  1. export default function Invoices() {
  2. return (
  3. <main style={{ padding: "1rem 0" }}>
  4. <h2>Invoices</h2>
  5. </main>
  6. );
  7. }

在 main.jsx 中添加 Route, 告诉浏览器如何根据不同的 URL 来渲染我们的 app

  1. import { render } from "react-dom";
  2. import App from "./App";
  3. import {
  4. BrowserRouter,
  5. Routes,
  6. Route
  7. } from "react-router-dom";
  8. import Expenses from "./routes/expenses";
  9. import Invoices from "./routes/invoices";
  10. const rootElement = document.getElementById("root");
  11. render(
  12. <BrowserRouter>
  13. <Routes>
  14. <Route path="/" element={<App />} />
  15. <Route path="expenses" element={<Expenses />} />
  16. <Route path="invoices" element={<Invoices />} />
  17. </Routes>
  18. </BrowserRouter>,
  19. rootElement
  20. );

这样设置路由会有一个问题: 当点击一个 Link 时,整个页面都会变成对应组件的内容,从 App 组件跳转到 Invoices 组件时,App 组件的导航栏就看不到了
image.png

嵌套路由

为了解决组件间共享布局的问题,我们可以使用嵌套路由,让子组件在父组件的某个区域内渲染

将子组件的 Route 嵌套到父组件的 Route 里面

  1. <Routes>
  2. <Route path="/" element={<App />}>
  3. <Route path="expenses" element={<Expenses />} />
  4. <Route path="invoices" element={<Invoices />} />
  5. </Route>
  6. </Routes>

此时再点击 Link 只有 URL 改变,并不会渲染对应的组件
在父组件中添加 Outlet 就能渲染子组件了

  1. import { Link, Outlet } from "react-router-dom";
  2. export default function App() {
  3. return (
  4. <div>
  5. <h1>Bookkeeper!</h1>
  6. <nav style={{
  7. borderBottom: "solid 1px",
  8. paddingBottom: "1rem",
  9. }}>
  10. <Link to="/invoices">Invoices</Link>
  11. <Link to="/expenses">Expenses</Link>
  12. </nav>
  13. <Outlet />
  14. </div>
  15. );
  16. }

image.png

添加 Invoices 列表

创建src/data.js

  1. let invoices = [
  2. {
  3. name: 'Santa Monica',
  4. number: 1995,
  5. amount: '$10,800',
  6. due: '12/05/1995',
  7. },
  8. {
  9. name: 'Stankonia',
  10. number: 2000,
  11. amount: '$8,000',
  12. due: '10/31/2000',
  13. },
  14. {
  15. name: 'Ocean Avenue',
  16. number: 2003,
  17. amount: '$9,500',
  18. due: '07/22/2003',
  19. },
  20. {
  21. name: 'Tubthumper',
  22. number: 1997,
  23. amount: '$14,000',
  24. due: '09/01/1997',
  25. },
  26. {
  27. name: 'Wide Open Spaces',
  28. number: 1998,
  29. amount: '$4,600',
  30. due: '01/27/1998',
  31. },
  32. ]
  33. export function getInvoices() {
  34. return invoices
  35. }
  1. import { Link } from "react-router-dom"
  2. import { getInvoices } from "../data"
  3. export default function Invoices() {
  4. let invoices = getInvoices()
  5. return (
  6. <div style={{display: "flex"}}>
  7. <nav style={{
  8. borderRight: "solid 1px",
  9. padding: "1rem"
  10. }}>
  11. {invoices.map((invoice) => (
  12. <Link style={{display: "block", margin: "1rem 0"}}
  13. to={`/invoices/${invoice.number}`}
  14. key={invoice.number}>
  15. {invoice.name}
  16. </Link>
  17. ))}
  18. </nav>
  19. </div>
  20. )
  21. }

点击 Link 跳转到对应的 URL, 此时页面为空白,因为我们没有设置与该 URL 匹配的路由
image.png

添加 404 not found

当其他 route 都不匹配时, 就会与 * 匹配

  1. // ...
  2. const rootElement = document.getElementById("root");
  3. render(
  4. <BrowserRouter>
  5. <Routes>
  6. <Route path="/" element={<App />}>
  7. <Route path="expenses" element={<Expenses />} />
  8. <Route path="invoices" element={<Invoices />} />
  9. <Route
  10. path="*"
  11. element={
  12. <main style={{padding: "1rem"}}>
  13. <p>There's nothing here!</p>
  14. </main>
  15. } />
  16. </Route>
  17. </Routes>
  18. </BrowserRouter>,
  19. rootElement
  20. );

读取URL参数(params)

创建 Inovice 组件

  1. export default function Invoice() {
  2. return <h2>Invoice #???</h2>
  3. }

在 Invoices 的 Route 中添加子 Route

  1. <Route path="invoices" element={<Invoices />} >
  2. <Route path=":invoiceId" element={<Invoice />} />
  3. </Route>

在 Invoices 组件中添加 Outlet 组件, 当点击子路由时就会显示子组件
image.png
使用 useParams 获取 Route 中的参数 :invoiceId

  1. import { useParams } from "react-router-dom"
  2. export default function Invoice() {
  3. let params = useParams()
  4. return <h2>Invoice: {params.invoiceId}</h2>
  5. }

index Route

image.png
当没有点击 Invoices 组件内的 list 时, Invoices 的 Outlet 区域显示为空白,我们可以添加一个 index route 来指定 Outlet 区域默认显示的内容

  1. <Route path="invoices" element={<Invoices />} >
  2. <Route
  3. index
  4. element={
  5. <main style={{padding: "1rem"}}>
  6. <p>Select an invoice</p>
  7. </main>
  8. }
  9. ></Route>
  10. <Route path=":invoiceId" element={<Invoice />} />
  11. </Route>

image.png
index Route 没有 path 属性, 它的 path 就是父Route的path, 当父Route被选择而子Route未被选择,则父Route的 Outlet 里默认显示 index Route 的内容

Active Links

使用 Link 标签无法知道 Link 是否被选中,将 Link 改为使用 NavLink

  1. <NavLink
  2. style={( {isActive} ) => (
  3. {
  4. display: "block",
  5. margin: "1rem 0",
  6. color: isActive ? "red" : ""
  7. })}
  8. to={`/invoices/${invoice.number}`}
  9. key={invoice.number}>
  10. {invoice.name}
  11. </NavLink>

style 在这里变成了一个函数,参数是一个有 isActive 属性的对象
被选中的 NavLink 会被添加上 active 类名
image.png
NavLink 的 className 也可以使用函数

  1. // normal string
  2. <NavLink className="red" />
  3. // function
  4. <NavLink className={({ isActive }) => isActive ? "red" : "blue"} />

search params

URL 除了传递参数,还可以传递查询字符串,例如:/shoes?brand=nike&sort=asc&sortby=price
使用useSearchParams可以读取和修改查询字符串

  • useSearchParams的用法类似于useState
  • searchParams.get() 方法获得查询字符串指定属性的值
  • setSearchParams() 修改查询字符串
  • image.png ```jsx import { Link, NavLink, Outlet, useSearchParams } from “react-router-dom” import { getInvoices } from “../data”

export default function Invoices() { let invoices = getInvoices() let [searchParams, setSearchParams] = useSearchParams()

return (