React Router 是 React 的一个标准路由库。当你想要浏览具有多个视图的 React 应用时,你需要一个路由功能来管理 URL。React Router 就能做到这一点,让你应用的 UI 和 URL 保持同步。
本教程将向您介绍 React Router v6 以及用它可以做到的许多事情。
引言
React 是一个流行的 JavaScript 库,用于构建可提供动态内容的交互式 Web 应用。此类应用可能有多个视图(又称页面),但与传统的多页面应用不同的是,浏览这些视图不会触发整个页面的重新加载,而是在当前页面中直接渲染出来。
对于习惯于使用多页面应用的终端用户来说,他们希望单页应用具备以下功能:
- 每个视图都应有一个唯一 URL。这样,用户就可以将 URL 加入书签,供以后直接打开 --- 例如,
www.example.com/products。 - 浏览器的 "后退 "和 "前进 "按钮应能正常工作。
- 动态生成的嵌套视图最好也有自己的 URL,例如
example.com/products/shoes/101,其中 101 是产品 ID。
路由提供让浏览器 URL 与页面渲染内容保持同步的能力。React Router 可让你声明式地处理路由,声明式路由方法允许你通过说 "路由应该是这样的" 来控制应用中的页面和路由绑定。
js
<Route path="/about" element={<About />} />
你可以将 <Route> 组件放在任何位置,它都能按你期望正确渲染内容。因为 <Route>、<Link> 和我们将要处理的所有 React Router 其他 API 都只是组件,因此你可以轻松地在 React 中接入路由。
注意:大家普遍误认为 React Router 是由 Facebook 开发的官方路由解决方案。实际上,它是 Remix Software 开发和维护的第三方库。
概述
本教程分为不同部分。首先,我们将使用 npm 安装 React 和 React Router。然后,我们将直接开始接触一些基础知识。你将看到在实际应用中 React Router 的不同代码示例。本教程涵盖的示例包括:
- 基本导航路由
- 嵌套路由
- 带路径参数的嵌套路由
- 受保护路由
所有概念都会在构建这些示例的过程中介绍。
该项目的全部代码都可以在 GitHub 上找到。
让我们开始吧!
安装 React Router
要学习本教程,你需要在电脑上安装最新版本的 Node。如果还没安装,请访问 Node 主页,根据你的系统下载正确的二进制文件。或者,你也可以考虑使用版本管理器来安装 Node。我们在此处提供了使用版本管理器的教程。
Node 捆绑了 npm,它是 JavaScript 的包管理器,我们将用它来安装一些要使用的库。有关 npm 的更多信息,请点击此处。
你可以在命令行中执行以下命令,检查两者是否都已正确安装:
node
node -v
> 20.9.0
npm -v
> 10.1.0
完成上述步骤后,让我们使用 Create React App 工具创建一个新的 React 项目。你可以全局安装,也可以使用 npx,就像这样:
js
npx create-react-app react-router-demo
完成后,切换到新创建的目录:
js
cd react-router-demo
React Router 由三个软件包组成:react-router、react-router-dom 和 react-router-native。核心包是 react-router,而其他两个包则针对具体环境。如果你正在构建 Web 应用,就应该使用 react-router-dom;如果你是在用 React Native 开发移动应用,就应该使用 react-router-native。
使用 npm 安装 react-router-dom 软件包:
js
npm install react-router-dom
然后启动开发服务器:
js
npm run start
恭喜。 你现在拥有了一个安装了 React Router 的可运行 React 应用。您可以在 http://localhost:3000/ 上查看程序的运行情况。
React Router 基础知识
Router 组件
我们需要做的第一件事是用 <Router> 组件(由 React Router 提供)来包裹 <App> 组件。路由有多种类型,在我们的案例中,有两种路由值得考虑:
它们之间的主要区别体现在所创建的 URL 上:
js
// <BrowserRouter>
https://example.com/about
// <HashRouter>
https://example.com/#/about
<BrowserRouter> 是一种常用的路由,它利用 HTML5 History API 将用户界面与 URL 同步,提供了一种没有 hash 片段的更简洁的 URL 结构。而 <HashRouter> 利用 URL 的 hash 部分(window.location.hash)来管理路由,它的优势在于无需对服务器增加配置和优秀的兼容性。你可以在此阅读有关差异的更多信息。
还请注意,在 React Router 的最新版本(v6.4)中引入了四个新的路由,它们支持各种新的数据 API。在本教程中,我们将重点介绍传统路由,因为这些路由功能强大、文档齐全,而且在众多项目中都有使用。不过,我们将在后面的章节中深入介绍 v6.4 中的新功能。
因此,让我们导入 <BrowserRouter> 组件,并用它包裹 <App> 组件。将 index.js 改为如下所示:
js
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
这段代码为我们的整个 <App> 组件创建了一个 history 实例。让我们看看这意味着什么。
History 小知识
history库能让你在 JavaScript 中轻松管理会话历史。history对象抽象化了各种环境中的差异,并提供了一个最小化的 API,让你可以管理历史记录堆栈、导航,并在会话之间持续保持状态。------ remix-run
每个 <Router> 组件都会创建一个 history 对象,用于跟踪堆栈中的当前路由地址和前一个路由地址。当当前路由地址发生变化时,视图就会重新渲染,从而让你有一种导航的感觉。
如何更改当前路由地址?在 React Router v6 中,useNavigate Hook 提供了一个路由跳转的函数: navigate。当你点击 <Link> 组件时会调用 navigate 函数,也可以通过传递带有 replace: true 属性的选项对象来覆盖当前路由地址。
其他方法(如 navigate(-1) 用于后退,navigate(1) 用于前进)可用于通过后退或前进一页来浏览历史堆栈。
应用无需创建自己的历史对象;这项任务由 <Router> 组件处理。简而言之,它会创建一个 history 对象,订阅堆栈中的更改,并在 URL 更改时修改其状态。这会触发程序的重新渲染,确保显示正确的用户界面。
接下来是 Links 和 Routes。
Link 和 Route 组件
<Route> 组件是 React Rtouter 中最重要的组件。如果路由地址和当前 URL 路径匹配,它就会在界面渲染一些内容。正常情况下,<Route> 组件应该有一个名为 path 的属性,如果这个属性值与当前 URL 路径匹配,组件内容就会被渲染。
而 <Link> 组件则用于在页面之间导航。它类似于 HTML 锚点元素(<a>)。不过,使用锚链接会导致整个页面刷新,这是我们不希望看到的。因此,我们可以使用 <Link> 来导航到一个特定的 URL,并在不刷新的情况下重新渲染视图。
现在,我们的程序应该能正常运行了。删除项目 src 文件夹中除 index.js 和 App.js 以外的所有文件,然后按如下步骤更新 App.js:
js
import { Link, Route, Routes } from 'react-router-dom';
const Home = () => (
<div>
<h2>Home</h2>
<p>Welcome to our homepage!</p>
</div>
);
const Categories = () => (
<div>
<h2>Categories</h2>
<p>Browse items by category.</p>
</div>
);
const Products = () => (
<div>
<h2>Products</h2>
<p>Browse individual products.</p>
</div>
);
export default function App() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/categories">Categories</Link>
</li>
<li>
<Link to="/products">Products</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/categories" element={<Categories />} />
<Route path="/products" element={<Products />} />
</Routes>
</div>
);
}
在这里,我们声明了三个组件:<Home>、<Categories> 和 <Products>,它们分别代表应用中的不同页面。从 React Router 中导入的 <Routes> 和 <Route> 组件用于定义路由逻辑。
在 <App> 组件中,我们有一个基本的导航菜单,其中每个菜单项都是 React Router 中的 <Link> 组件。<Link> 组件用于创建导航链接,每个链接分别与特定路径(/、/categories 和 /products)相关联。请注意,在一个较大的应用中,这个菜单可以封装在一个布局组件中,以便在不同的视图中保持一致的结构。你可能还想为当前选定的导航项添加某种激活态类名(如使用 NavLink 组件)。不过,为了专注基础知识,我们在此略过这块内容。
在导航菜单代码下方,<Routes> 组件作为容器,内部包括多个 <Route> 组件。每个 <Route> 组件都有路径和 React 组件参数,当路径与当前 URL 匹配时,React 组件将被呈现。例如,当 URL 为 /categories 时,将呈现 <Categories> 组件。
注意:在以前版本的 React Router 中,/ 会同时匹配 / 和 /categories,这意味着两个组件都会被渲染。解决这个问题的办法是给 指定 exact 属性,确保只匹配精确路径。这种行为在 v6 版本中有所改变,现在默认情况下所有路径都是完全匹配的。正如我们将在下一节看到的,如果因为有子路由而想匹配更多的 URL,可以使用尾部的 *,例如 <Route path="categories/*" ...>。
如果你有在跟随教程运行代码,请花点时间点击一下程序,确保一切都符合预期。
嵌套路由
顶级路由固然很好,但不久之后,大多数应用程序都需要嵌套路由,例如,显示特定产品或编辑特定用户。
在 React Router v6 中,路由是通过在 JSX 代码中将 <Route> 组件置于其他 <Route> 组件内来嵌套的。这样,嵌套的 <Route> 组件就自然反映了它们所代表的 URL 的嵌套结构。
让我们看看如何在应用中实现这一点。像这样更改 App.js(其中...表示前面的代码保持不变):
js
import { Link, Route, Routes } from 'react-router-dom';
import { Categories, Desktops, Laptops } from './Categories';
const Home = () => ( ... );
const Products = () => ( ... );
export default function App() {
return (
<div>
<nav>...</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/categories/" element={<Categories />}>
<Route path="desktops" element={<Desktops />} />
<Route path="laptops" element={<Laptops />} />
</Route>
<Route path="/products" element={<Products />} />
</Routes>
</div>
);
}
如你所见,我们将 <Categories> 组件移到了单独的文件中,现在又从其文件导入了两个组件,即 <Desktops> 和 <Laptops>。
我们还对 <Routes> 组件进行了一些修改,稍后再看这块内容。
首先,在与 App.js 文件相同的文件夹中创建 Categories.js 文件。然后添加以下代码:
js
// src/Categories.js
import { Link, Outlet } from 'react-router-dom';
export const Categories = () => (
<div>
<h2>Categories</h2>
<p>Browse items by category.</p>
<nav>
<ul>
<li>
<Link to="desktops">Desktops</Link>
</li>
<li>
<Link to="laptops">Laptops</Link>
</li>
</ul>
</nav>
<Outlet />
</div>
);
export const Desktops = () => <h3>Desktop PC Page</h3>;
export const Laptops = () => <h3>Laptops Page</h3>;
刷新应用(如果开发服务器正在运行,则会自动刷新),然后点击 Categories 链接。现在,你应该可以看到两个新的菜单项(Desktops 和 Laptops ),点击其中任何一个都会在原 Categories 页面内显示一个新内容。
我们刚才做了什么?
在 App.js 中,我们将 /categories 路由改成这样:
js
<Route path="/categories/" element={<Categories />}>
<Route path="desktops" element={<Desktops />} />
<Route path="laptops" element={<Laptops />} />
</Route>
在更新后的代码中,/categories 的 <Route> 组件已被修改为包含两个嵌套的 <Route> 组件 ------ 一个是 /categories/desktops ,另一个是 /categories/laptops。这一修改说明了 React Router 如何通过路由配置实现路由组合。
通过在 /categories <Route> 中嵌套 <Route> 组件,我们可以创建结构更合理的 URL 和 UI 层次结构。这样,当用户导航到 /categories/desktops 或 /categories/laptops 时,相应的 <Desktops> 或 <Laptops> 组件将在 <Categories> 组件中渲染,从而清晰地展示出各个路由和组件之间的父子关系。
注意:嵌套路由的路径是由其祖先的路径和自身的路径连接而成的。
我们还修改了 <Categories> 组件,使其包含一个 <Outlet />:
js
export const Categories = () => (
<div>
<h2>Categories</h2>
...
<Outlet />
</div>
);
<Outlet> 位于父路由元素中,用于渲染其子路由元素。这样就可以在渲染子路由时显示嵌套的界面。
这种组合式方法使路由配置更具声明性,更易于理解,与 React 基于组件的架构非常吻合。
使用 Hook 访问路由属性
在之前版本中,某些属性是隐式传递给组件的。例如:
js
const Home = (props) => {
console.log(props);
return ( <h2>Home</h2> );
};
上述代码将输出如下内容:
js
{
history: { ... },
location: { ... },
match: { ... }
}
在 React Router 第 6 版中,传递路由属性的方法发生了变化,提供了一种更明确的基于 Hook 的方法。路由属性 history、location 和 match 不再隐式传递给组件。相反,现在使用一组 Hook 来访问这些信息。
例如,要访问 location 对象,可以使用 useLocation Hook。useMatch Hook 会返回匹配到的路径参数。history 对象不再显式使用,而是通过 useNavigate Hook 返回一个函数,让你以编程方式进行导航。
还有更多 Hook 值得探索,我不在此一一列举,而是建议你查看官方文档,在左侧边栏中可以找到可用的 Hook。
接下来,让我们更详细地了解其中一个 Hook,使我们之前的示例更具活力。
嵌套动态路由
首先,像这样更改 App.js 中的路由:
js
<Routes>
<Route path="/" element={<Home />} />
<Route path="/categories/" element={<Categories />}>
<Route path="desktops" element={<Desktops />} />
<Route path="laptops" element={<Laptops />} />
</Route>
<Route path="/products/*" element={<Products />} />
</Routes>
眼尖的人会发现,/products 路由的尾部多了一个 /*。在 React Router 第 6 版中,/* 是表示 <Products> 组件可以有子路由的一种方式,而且它还是 URL 中 /products 之后可能出现的任何其他路径段的占位符。这样,当你导航到 /products/laptops 这样的 URL 时,<Products> 组件仍将被匹配和渲染,并能使用自己的嵌套 <Route> 元素进一步处理路径中的 laptops 部分。
接下来,让我们把 <Products> 组件移到它自己的文件中:
js
// src/App.js
...
import Products from './Products';
const Home = () => ( ... );
export default function App() { ... }
最后,创建一个 Products.js 文件并添加以下代码:
js
// src/Products.js
import { Route, Routes, Link, useParams } from 'react-router-dom';
const Item = () => {
const { name } = useParams();
return (
<div>
<h3>{name}</h3>
<p>Product details for the {name}</p>
</div>
);
};
const Products = () => (
<div>
<h2>Products</h2>
<p>Browse individual products.</p>
<nav>
<ul>
<li>
<Link to="dell-optiplex-3900">Dell OptiPlex 3090</Link>
</li>
<li>
<Link to="lenovo-thinkpad-x1">Lenovo ThinkPad X1</Link>
</li>
</ul>
</nav>
<Routes>
<Route path=":name" element={<Item />} />
</Routes>
</div>
);
export default Products;
在这里,我们在 <Route>(在页面顶部声明) 添加 <Item> 组件中。路由的路径设置为 :name,这将匹配其父路由之后的任何路径,并将该路径作为名为 name 的参数传递给 <Item> 组件。
在 <Item> 组件中,我们使用了 useParams Hook。它会返回当前 URL 中动态参数的键/值对对象。在 /products/laptops 路径下,如果我们参数输出到控制台,我们会看到:
js
Object { "*": "laptops", name: "laptops" }
我们可以使用对象解构来直接获取该参数,然后将其渲染在 <h3> 标记中。
试试看! 正如你所看到的,<Item> 组件会捕捉你在导航栏中输入的任何链接,并动态创建一个页面。
你还可以尝试添加更多的菜单项:
js
<li>
<Link to="cyberpowerpc-gamer-xtreme">CyberPowerPC Gamer Xtreme</Link>
</li>
我们的应用将把这些新页面考虑在内。
这种捕捉 URL 动态片段并将其作为组件参数的方法可以让我们根据 URL 结构实现更灵活的路由和组件渲染。
让我们在下一节中继续学习。
嵌套路由与路径参数
在实际应用中,路由必须处理数据并动态显示数据。假设我们有一些由 API 接口返回的产品数据,格式如下:
js
const productData = [
{
id: 1,
name: "Dell OptiPlex 3090",
description:
"The Dell OptiPlex 3090 is a compact desktop PC that offers versatile features to meet your business needs.",
status: "Available",
},
{
id: 2,
name: "Lenovo ThinkPad X1 Carbon",
description:
"Designed with a sleek and durable build, the Lenovo ThinkPad X1 Carbon is a high-performance laptop ideal for on-the-go professionals.",
status: "Out of Stock",
},
{
id: 3,
name: "CyberPowerPC Gamer Xtreme",
description:
"The CyberPowerPC Gamer Xtreme is a high-performance gaming desktop with powerful processing and graphics capabilities for a seamless gaming experience.",
status: "Available",
},
{
id: 4,
name: "Apple MacBook Air",
description:
"The Apple MacBook Air is a lightweight and compact laptop with a high-resolution Retina display and powerful processing capabilities.",
status: "Out of Stock",
},
];
我们假设还需要以下路径的路由:
/products:显示产品列表。/products/:productId:如果存在带有:productId的产品,则应显示产品数据;如果不存在,则应显示错误信息。
用以下内容替换 Products.js 的当前内容(确保复制了上面的产品数据):
js
import { Link, Route, Routes } from "react-router-dom";
import Product from "./Product";
const productData = [ ... ];
const Products = () => {
const linkList = productData.map((product) => {
return (
<li key={product.id}>
<Link to={`${product.id}`}>{product.name}</Link>
</li>
);
});
return (
<div>
<h3>Products</h3>
<p>Browse individual products.</p>
<ul>{linkList}</ul>
<Routes>
<Route path=":productId" element={<Product data={productData} />} />
<Route index element={<p>Please select a product.</p>} />
</Routes>
</div>
);
};
export default Products;
在组件内部,我们使用每个产品的 id 属性建立一个 <Link> 组件列表。我们将其存储在 linkList 变量中,然后再将其渲染到页面上。
接下来是两个 <Route> 组件。第一个有 path 属性,值为 :productId,(如前所述)这是一个路由参数。这样,我们就可以使用该 URL 中 productId 值。此 <Route> 组件的 element 属性被设置为 <Product> 组件,并将 productData 数组作为属性传递给它。只要 URL 匹配上,就会渲染 <Product> 组件,并从 URL 中获取到相应的 productId。
第二个 <Route> 组件使用 index 属性,只要 URL 与基础路径完全匹配,就会渲染文本 "请选择产品"。index 参数表示此路由是 <Routes> 设置中的基础路由或 "默认" 路由。因此,当 URL 与基础路径(即 /products)相匹配时,就会显示这条信息。
现在,我们来看看上面提到的 <Product> 组件的代码。你需要创建该文件 - src/Product.js:
js
import { useParams } from 'react-router-dom';
const Product = ({ data }) => {
const { productId } = useParams();
const product = data.find((p) => p.id === Number(productId));
return (
<div>
{product ? (
<div>
<h3> {product.name} </h3>
<p>{product.description}</p>
<hr />
<h4>{product.status}</h4>
</div>
) : (
<h2>Sorry. Product doesn't exist.</h2>
)}
</div>
);
};
export default Product;
在这里,我们使用 useParams Hook 以键/值对的形式访问 URL 路径的动态部分。然后我们使用解构来获取我们想要的数据(productId)。
在 data 数组中使用 find 方法搜索并返回第一个 id 属性与从 URL 参数中获取的 productId 匹配的元素。
现在,当你在浏览器中访问应用并选择产品时,就会看到一个子菜单,该菜单会显示产品数据。
在继续之前,请先试玩一下 demo 程序。确保一切正常,并了解代码中发生了什么。
受保护的路由
许多现代 web 应用的一个共同要求是确保只有登录用户才能访问网站的某些部分。在下一节中,我们将介绍如何实现受保护路由,这样如果有人试图访问 /admin,就会被要求登录。
不过,我们需要先了解 React Router 的几个知识点。
在 React Router v6 中通过编程手动导航
在第 6 版中,可通过 useNavigate Hook 以编程方式重定向到新地址。该 Hook 提供了一个函数,可用于以编程方式导航到不同的路径。它可以接受一个对象作为第二个参数,用于指定各种选项。例如:
js
const navigate = useNavigate();
navigate('/login', {
state: { from: location },
replace: true
});
这将把用户重定向到 /login,同时传递一个 location 值以存储在历史状态中,然后我们可以通过 useLocation Hook 在目标路径上访问该值。指定 replace: true 还将替换历史堆栈中的当前条目,而不是添加新条目。这模仿了 v5 中现已停用的 <Redirect> 组件的行为。
概括地说:如果有人在登出状态下试图访问 /admin 路由,他们将被重定向到 /login 路由。有关当前位置的信息通过 state 属性传递,因此如果身份验证成功,用户就会被重定向到他们最初试图访问的页面。
自定义路由
接下来我们需要了解的是自定义路由。React Router 中的自定义路由是一个用户定义的组件,可以在路由过程中实现额外的功能或行为。它可以封装特定的路由逻辑(如身份验证检查),并根据特定条件渲染不同的组件或执行操作。
在 src 目录中创建一个新文件 PrivateRoute.js,并添加以下内容:
js
import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { fakeAuth } from './Login';
const PrivateRoute = ({ children }) => {
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
if (!fakeAuth.isAuthenticated) {
navigate('/login', {
state: { from: location },
replace: true,
});
}
}, [navigate, location]);
return fakeAuth.isAuthenticated ? children : null;
};
export default PrivateRoute;
这里有几个步骤。首先,我们导入了一个名为 fakeAuth 的东西,它提供了一个 isAuthenticated 属性。我们很快就会更详细地了解这个属性,但现在只要知道我们将用它来确定用户的登录状态就足够了。
该组件接受一个 children 属性。这将是 <PrivateRoute> 组件被调用时包裹的受保护内容。例如:
js
<PrivateRoute>
<Admin /> <-- children
</PrivateRoute>
在组件主体中,useNavigate 和 useLocation Hook 分别用于获取 navigate 函数和当前 location 对象。如果用户未通过 !fakeAuth.isAuthenticated 检查,则会调用 navigate 函数将用户重定向到 /login 路由,如上一节所述。
组件的返回语句会再次检查身份验证状态。如果用户已通过身份验证,则渲染其子组件。如果用户未通过身份验证,则返回空值,不会渲染任何内容。
还请注意,我们是在 React 的 useEffect Hook 中调用 navigate。这是因为 navigate 函数不应直接在组件主体内部调用,因为它会在渲染过程中触发状态更新。将其写在 useEffect Hook 中可确保在组件渲染后调用。
重要安全声明
在实际应用中,对服务器上受保护资源的任何请求,你都需要做认证。让我再说一遍...
在实际应用中,对服务器上受保护资源的任何请求,你都需要做认证。
这是因为在客户端上运行的任何程序都有可能被反编译和篡改。例如,在上面的代码中,只要打开 React 的开发工具,将 isAuthenticated 的值改为 true,就可以访问受保护的内容。
React 应用中的身份验证值得单独编写教程,但实现身份验证的一种方法是使用 JSON Web Token。例如,你可以在服务器上设置一个接受用户名和密码的接口。当它收到这些信息(通过 Ajax)时,它会检查凭证是否有效。如果有效,它会响应一个 JWT,React 应用程序会保存该 JWT(例如,保存在 sessionStorage 中);如果无效,它会向客户端发送一个 401 Unauthorized 响应。
假设登录成功,客户端就会将 JWT 作为请求头信息与任何受保护资源请求一起发送。然后,服务器在发送响应之前会对其进行验证。
在存储密码时,服务器不会以明文形式存储 。相反,它会对密码进行加密,例如使用 bcryptjs。
实现受保护的路由
现在,让我们实现受保护的路由。像这样修改 App.js:
js
import { Link, Route, Routes } from 'react-router-dom';
import { Categories, Desktops, Laptops } from './Categories';
import Products from './Products';
import Login from './Login';
import PrivateRoute from './PrivateRoute';
const Home = () => (
<div>
<h2>Home</h2>
<p>Welcome to our homepage!</p>
</div>
);
const Admin = () => (
<div>
<h2>Welcome admin!</h2>
</div>
);
export default function App() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/categories">Categories</Link>
</li>
<li>
<Link to="/products">Products</Link>
</li>
<li>
<Link to="/admin">Admin area</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/categories/" element={<Categories />}>
<Route path="desktops" element={<Desktops />} />
<Route path="laptops" element={<Laptops />} />
</Route>
<Route path="/products/*" element={<Products />} />
<Route path="/login" element={<Login />} />
<Route
path="/admin"
element={
<PrivateRoute>
<Admin />
</PrivateRoute>
}
/>
</Routes>
</div>
);
}
如你所见,我们在文件顶部添加了 <Admin> 组件,并在 <Routes> 组件中使用了 <PrivateRoute> 。如前所述,如果用户已登录,该自定义路由将渲染 <Admin> 组件。否则,用户将被重定向到 /login。
最后,创建 Login.js,并添加以下代码:
js
// src/Login.js
import { useState, useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
export default function Login() {
const navigate = useNavigate();
const { state } = useLocation();
const from = state?.from || { pathname: '/' };
const [redirectToReferrer, setRedirectToReferrer] = useState(false);
const login = () => {
fakeAuth.authenticate(() => {
setRedirectToReferrer(true);
});
};
useEffect(() => {
if (redirectToReferrer) {
navigate(from.pathname, { replace: true });
}
}, [redirectToReferrer, navigate, from.pathname]);
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={login}>Log in</button>
</div>
);
}
/* A fake authentication function */
export const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100);
},
};
在上面的代码中,我们试图获取用户在被要求登录之前要访问的 URL 的值。如果不存在,我们就将其设置为 { pathname:"/" }。
然后,我们使用 React 的 useState Hook 将 redirectToReferrer 属性初始化为 false。根据该属性的值,用户要么会被重定向到他们要去的地方(即用户已登录),要么用户会看到一个登录按钮。
一旦按钮被点击,fakeAuth.authenticate 方法就会被执行,它会将 fakeAuth.isAuthenticated 设置为 true,并(在回调函数中)将 redirectToReferrer 的值更新为 true。这将触发组件重新渲染,用户被重定向。
可运行的 Demo
让我们把所有内容拼起来。下面是我们使用 React 路由构建的应用的最终示例。
React Router 6.4
在结束之前,我们应该提到 React Router v6.4 的发布。尽管看起来只是一个不起眼的版本,但这个版本引入了一些突破性的新功能。例如,它现在包含了 Remix 中的数据加载和可变(mutation) API,为用户界面与数据保持同步引入了全新的范式。
从 6.4 版开始,您可以为每个路由定义一个 loader 函数,负责获取该路由所需的数据。在组件中,你可以使用 useLoaderData Hook 来访问加载的数据。当用户导航到路由时,React Router 会自动调用相关的 loader 函数来获取数据,并通过 useLoaderData 钩子将数据传递给组件,而无需使用任何 useEffect。这促进了数据获取与路由直接绑定的模式。
此外还有一个新的 <Form> 组件,它可以防止浏览器将请求发送到服务器,而是将其发送到路由的 action 中。React Router 会在 action 完成后自动重新验证页面上的数据,这意味着所有 useLoaderData hooks 都会更新,用户界面也会自动与数据保持同步。
要使用这些新的 APIS,你需要使用新的 <RouterProvider /> 组件。这需要使用新的 createBrowserRouter 函数创建一个 router 参数。
详细讨论所有这些更改超出了本文的讨论范围,但如果你想了解更多信息,我建议您阅读 React Router 官方教程。
总结
正如你在本文中所看到的,React Router 是一个功能强大的库,它与 React 相辅相成,可以在你的 React 应用中构建更好的声明式路由。在撰写本文时,React Router 的当前版本是 v6.18,自 v5 版本以来,该库已发生了巨大的变化,部分原因是受到了 Remix 的影响,Remix 是由同一作者编写的全栈 Web 框架。
在本教程中,我们学习了:
- 如何设置和安装 React 路由
- 路由的基础知识和一些基础组件,例如
<Routes>、<Route>和<Link> - 如何创建一个小的路由系统,其中包含导航路由和嵌套路由
- 如何使用路径参数创建动态路由
- 如何使用 React 路由的 Hook 及其更新的路由渲染模式
最后,在编写受保护路由的最终示例时,我们学习了一些高级路由技术。
FAQs
React Router v6 中的路由是如何工作的?
该版本推出了 <Routes> 和 <Route> 的新路由语法。<Routes> 组件包裹 <Route> 组件,这些组件指定了路径和路径与 URL 匹配时要渲染的元素。
如何设置嵌套路由?
嵌套路由是通过在 JSX 代码中将 <Route> 组件置于其他 <Route> 组件内来创建的。这样,嵌套的 <Route> 组件就自然地反映了它们所代表的 URL 的嵌套结构。
如何将用户重定向到另一个页面?
你可以使用 useNavigate hook 以编程方式将用户导航到另一个页面。例如,const navigate = useNavigate(); 然后使用 navigate('/path'); 重定向到想要跳转的路径。
如何将参数传递给组件?
在第 6 版中,你可以在 <Route> 组件的 element 属性中把参数传递给组件,就像这样: <Route path="/path" element={<Component prop={value} />} />.
如何在 React Router v6 中访问 URL 参数?
可以使用 useParams hook 访问 URL 参数。例如,如果路由定义为 <Route path=":id" element={<Component />} />,则可以使用 const { id } = useParams(); 访问 <Component /> 内的 id 参数。
React Router v6.4 有哪些新特性?
6.4 版引入了许多受 Remix 启发的新功能,如数据加载器(loader)和 createBrowserRouter,旨在改进数据获取和提交。你可以在这里找到新功能的详尽列表。
如何从 React Router v5 迁移到 v6?
迁移包括将路由配置更新为新的 <Routes> 和 <Route> 组件语法,将 hooks 和其他 API 方法更新为 v6 对应方法,以及处理应用中路由逻辑的任何破坏性更改。你可以在此处找到官方指南。