React Router 已经升级到了版本6, 与版本5有了挺大区别,让我们跟着官方教程来学习一下 React Router 6 的使用吧。
安装
使用 vite 创建一个项目
yarn create vite router-tutorial --tempalte react
安装 React Router 6
yarn add react-router-dom@6
编辑 App.jsx
export default function App() {
return (
<div>
<h1>Bookkeeper!</h1>
</div>
);
}
编辑 main.jsx
import { render } from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
render(<App />, rootElement);
连接URL
用 BrowserRouter 包裹住 App 组件, 应用就能与浏览器的 URL 连接起来了
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
const rootElement = document.getElementById("root");
render(
<BrowserRouter>
<App />
</BrowserRouter>,
rootElement
);
添加Links
Link 的作用是在不导致页面重新加载的情况下切换浏览器的URL
import { Link } from "react-router-dom";
export default function App() {
return (
<div>
<h1>Bookkeeper!</h1>
<nav style={{
borderBottom: "solid 1px",
paddingBottom: "1rem",
}}>
<Link to="/invoices">Invoices</Link>
<Link to="/expenses">Expenses</Link>
</nav>
</div>
);
}
添加 Routes
创建2个新文件
- src/routes/invoices.jsx
- src/routes/expenses.jsx
内容如下
export default function Expenses() {
return (
<main style={{ padding: "1rem 0" }}>
<h2>Expenses</h2>
</main>
);
}
export default function Invoices() {
return (
<main style={{ padding: "1rem 0" }}>
<h2>Invoices</h2>
</main>
);
}
在 main.jsx 中添加 Route, 告诉浏览器如何根据不同的 URL 来渲染我们的 app
import { render } from "react-dom";
import App from "./App";
import {
BrowserRouter,
Routes,
Route
} from "react-router-dom";
import Expenses from "./routes/expenses";
import Invoices from "./routes/invoices";
const rootElement = document.getElementById("root");
render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="expenses" element={<Expenses />} />
<Route path="invoices" element={<Invoices />} />
</Routes>
</BrowserRouter>,
rootElement
);
这样设置路由会有一个问题: 当点击一个 Link 时,整个页面都会变成对应组件的内容,从 App 组件跳转到 Invoices 组件时,App 组件的导航栏就看不到了
嵌套路由
为了解决组件间共享布局的问题,我们可以使用嵌套路由,让子组件在父组件的某个区域内渲染
将子组件的 Route 嵌套到父组件的 Route 里面
<Routes>
<Route path="/" element={<App />}>
<Route path="expenses" element={<Expenses />} />
<Route path="invoices" element={<Invoices />} />
</Route>
</Routes>
此时再点击 Link 只有 URL 改变,并不会渲染对应的组件
在父组件中添加 Outlet 就能渲染子组件了
import { Link, Outlet } from "react-router-dom";
export default function App() {
return (
<div>
<h1>Bookkeeper!</h1>
<nav style={{
borderBottom: "solid 1px",
paddingBottom: "1rem",
}}>
<Link to="/invoices">Invoices</Link>
<Link to="/expenses">Expenses</Link>
</nav>
<Outlet />
</div>
);
}
添加 Invoices 列表
创建src/data.js
let invoices = [
{
name: 'Santa Monica',
number: 1995,
amount: '$10,800',
due: '12/05/1995',
},
{
name: 'Stankonia',
number: 2000,
amount: '$8,000',
due: '10/31/2000',
},
{
name: 'Ocean Avenue',
number: 2003,
amount: '$9,500',
due: '07/22/2003',
},
{
name: 'Tubthumper',
number: 1997,
amount: '$14,000',
due: '09/01/1997',
},
{
name: 'Wide Open Spaces',
number: 1998,
amount: '$4,600',
due: '01/27/1998',
},
]
export function getInvoices() {
return invoices
}
import { Link } from "react-router-dom"
import { getInvoices } from "../data"
export default function Invoices() {
let invoices = getInvoices()
return (
<div style={{display: "flex"}}>
<nav style={{
borderRight: "solid 1px",
padding: "1rem"
}}>
{invoices.map((invoice) => (
<Link style={{display: "block", margin: "1rem 0"}}
to={`/invoices/${invoice.number}`}
key={invoice.number}>
{invoice.name}
</Link>
))}
</nav>
</div>
)
}
点击 Link 跳转到对应的 URL, 此时页面为空白,因为我们没有设置与该 URL 匹配的路由
添加 404 not found
当其他 route 都不匹配时, 就会与 * 匹配
// ...
const rootElement = document.getElementById("root");
render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route path="expenses" element={<Expenses />} />
<Route path="invoices" element={<Invoices />} />
<Route
path="*"
element={
<main style={{padding: "1rem"}}>
<p>There's nothing here!</p>
</main>
} />
</Route>
</Routes>
</BrowserRouter>,
rootElement
);
读取URL参数(params)
创建 Inovice 组件
export default function Invoice() {
return <h2>Invoice #???</h2>
}
在 Invoices 的 Route 中添加子 Route
<Route path="invoices" element={<Invoices />} >
<Route path=":invoiceId" element={<Invoice />} />
</Route>
在 Invoices 组件中添加 Outlet 组件, 当点击子路由时就会显示子组件
使用 useParams 获取 Route 中的参数 :invoiceId
import { useParams } from "react-router-dom"
export default function Invoice() {
let params = useParams()
return <h2>Invoice: {params.invoiceId}</h2>
}
index Route
当没有点击 Invoices 组件内的 list 时, Invoices 的 Outlet 区域显示为空白,我们可以添加一个 index route 来指定 Outlet 区域默认显示的内容
<Route path="invoices" element={<Invoices />} >
<Route
index
element={
<main style={{padding: "1rem"}}>
<p>Select an invoice</p>
</main>
}
></Route>
<Route path=":invoiceId" element={<Invoice />} />
</Route>
index Route 没有 path 属性, 它的 path 就是父Route的path, 当父Route被选择而子Route未被选择,则父Route的 Outlet 里默认显示 index Route 的内容
Active Links
使用 Link 标签无法知道 Link 是否被选中,将 Link 改为使用 NavLink
<NavLink
style={( {isActive} ) => (
{
display: "block",
margin: "1rem 0",
color: isActive ? "red" : ""
})}
to={`/invoices/${invoice.number}`}
key={invoice.number}>
{invoice.name}
</NavLink>
style 在这里变成了一个函数,参数是一个有 isActive 属性的对象
被选中的 NavLink 会被添加上 active 类名
NavLink 的 className 也可以使用函数
// normal string
<NavLink className="red" />
// function
<NavLink className={({ isActive }) => isActive ? "red" : "blue"} />
search params
URL 除了传递参数,还可以传递查询字符串,例如:/shoes?brand=nike&sort=asc&sortby=price
使用useSearchParams
可以读取和修改查询字符串
- useSearchParams的用法类似于useState
- searchParams.get() 方法获得查询字符串指定属性的值
- setSearchParams() 修改查询字符串
- ```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 (