React Router v6:初学者指南 – CodesCode
学习使用React Router,这是React中默认的标准路由库,来导航多视图React应用程序
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 允许您以声明方式处理路由。声明式路由方式允许您通过说“路由应该是这样的”来控制应用程序中的数据流:
<Route path="/about" element={<About />} />
您可以在希望呈现路由的任何位置放置您的 <Route>
组件。由于我们将使用的 <Route>
、<Link>
和其他所有 API 只是组件,因此您可以轻松开始使用 React 进行路由。
注意:有一种常见误解,即 React Router 是由 Facebook 开发的官方路由解决方案。实际上,它是一个第三方库,由 Remix Software 开发和维护。
概览
本教程分为不同的部分。首先,我们将使用 npm 设置 React 和 React Router。然后我们将立即深入了解一些基础知识。您将在本教程中找到 React Router 的各种示例。
本教程涵盖的示例包括:
- 基本导航路由
- 嵌套路由
- 带路径参数的嵌套路由
- 受保护的路由
我们将在路上讨论与构建这些路由相关的所有概念。
整个项目的代码可以在这个 GitHub 仓库中获取。
让我们开始吧!
设置 React Router
要按照本教程进行操作,您需要在计算机上安装最新版本的 Node。如果没有安装,请访问 Node 官方网站并下载适用于您系统的正确二进制文件。或者,您可以考虑使用版本管理器来安装 Node。关于使用版本管理器的教程请点击这里。
Node 预装了 npm,npm 是JavaScript的包管理器,我们将使用它来安装一些要使用的库。您可以在这里了解更多关于使用 npm 的信息。
您可以通过在命令行中输入以下命令来检查两者是否正确安装:
node -v> 20.9.0npm -v> 10.1.0
完成后,让我们从使用Create React App工具创建一个新的 React 项目开始。您可以全局安装它,也可以使用 npx
命令,如下所示:
npx create-react-app react-router-demo
完成后,切换到新创建的目录:
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
包:
npm install react-router-dom
然后使用以下命令启动开发服务器:
npm run start
恭喜!您现在拥有一个使用 React Router 的工作中的 React 应用程序。您可以在 http://localhost:3000/ 查看应用程序运行情况。
React Router 基础知识
现在让我们了解基本设置。为此,我们将创建一个具有三个独立视图的应用程序:主页(Home)、分类(Category)和产品(Products)。
Router
组件
首先,我们需要将我们的 <App>
组件包装在一个 <Router>
组件中(由 React Router 提供)。有多种可用的路由器,但在我们的情况下,有两种值得考虑:
它们之间的主要区别在于它们创建的 URL:
// <BrowserRouter>https://example.com/about// <HashRouter>https://example.com/#/about
通常使用 <BrowserRouter>
,它利用HTML5 History API将 UI 与 URL 同步,提供更清晰的 URL 结构而无需使用哈希片段。另一方面,<HashRouter>
则利用 URL 的哈希部分(window.location.hash
)来管理路由,这对于无法进行服务器配置或支持缺乏 HTML5 History API 的旧版浏览器环境非常有用。您可以在此处阅读更多关于两者之间的差异。
注意,在最新版本的React Router(v6.4)中引入了四个支持各种新的数据API的新路由器。在本教程中,我们将重点关注传统的路由器,因为它们功能强大、文档完善,并在互联网上的众多项目中被广泛使用。然而,我们将在稍后的部分深入探讨v6.4的新功能。
所以,让我们导入<BrowserRouter>
组件并将其包装在<App>
组件周围。将index.js
更改为以下内容:
// src/index.jsimport 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
库可以让您轻松管理JavaScript在任何地方运行时的会话历史。一个history
对象抽象了各种环境中的差异,并提供了一个最小的API,让您管理历史堆栈、导航和在会话间保持状态。- remix-run
每个<Router>
组件都会创建一个history
对象,用于跟踪当前位置和前一个位置的堆栈。当当前位置发生变化时,视图将重新渲染,您将感受到导航的效果。
当前位置是如何改变的呢?在React Router v6中,useNavigate钩子提供了一个navigate
函数,用于此目的。当您点击一个<Link>
组件时,会调用navigate
函数,它也可以用于替换当前位置,只需传递一个带有replace: true
属性的选项对象。
其他方法,比如navigate(-1)
用于返回上一页,navigate(1)
用于前进一页,用于通过历史堆栈进行导航。
应用程序不需要创建自己的历史对象;这个任务由<Router>
组件来管理。简而言之,它创建一个历史对象,订阅堆栈的变化,并在URL变化时修改其状态。这会触发应用程序的重新渲染,确保显示适当的用户界面。
接下来,我们有链接和路由。
Link
和Route
组件
<Route>
组件是React Router中最重要的组件。如果位置匹配当前路由路径,它会渲染一些用户界面。理想情况下,<Route>
组件应该有一个名为path
的属性,如果路径名称与当前位置匹配,则会被渲染。
另一方面,<Link>
组件用于在页面之间导航。它类似于HTML的锚元素。然而,使用锚链接会导致完整页面刷新,这是我们不想要的。因此,我们可以使用<Link>
导航到特定的URL,并在重新渲染视图时不刷新。
现在,我们已经介绍了使我们的应用程序工作所需的一切。从项目的src
文件夹中删除除index.js
和App.js
之外的所有文件,然后按以下方式更新App.js
:
import { Link, Route, Routes } from 'react-router-dom';const Home = () => ( <div> <h2>首页</h2> <p>欢迎来到我们的主页!</p> </div>);const Categories = () => ( <div> <h2>分类</h2> <p>按类别浏览物品。</p> </div>);const Products = () => ( <div> <h2>产品</h2> <p>浏览单个产品。</p> </div>);export default function App() { return ( <div> <nav> <ul> <li> <Link to="/">首页</Link> </li> <li> <Link to="/categories">分类</Link> </li> <li> <Link to="/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匹配时渲染该组件。例如,当URL是/categories
时,将渲染<Categories>
组件。
注意:在以前的React Router版本中,/
会匹配/
和/categories
,所以两个组件都会被渲染。解决这个问题的方法是在<Route>
中传递exact
属性,确保只匹配精确的路径。这个行为在v6中发生了改变,现在所有路径默认都完全匹配。正如我们将在下一节中看到的,如果你想匹配更多的URL,因为你有子路由,使用一个尾随的*
,例如<Route path="categories/*" ...>
。
如果您跟着实践操作,请花点时间点击应用程序的各个部分,确保一切都表现如预期。
嵌套路由
顶级路由当然是好的,但在不久的将来,大多数应用程序都需要能够嵌套路由,例如显示特定产品或编辑特定用户。
在React Router v6中,通过将<Route>
组件放置在JSX代码中的其他<Route>
组件内部来嵌套路由。这样,嵌套的<Route>
组件自然地反映了它们所代表的URL的嵌套结构。
让我们看看我们如何在我们的应用程序中实现这一点。像这样更改App.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
文件。然后添加以下代码:
// src/Categories.jsimport { Link, Outlet } from 'react-router-dom';export const Categories = () => ( <div> <h2>分类目录</h2> <p>按类别浏览物品。</p> <nav> <ul> <li> <Link to="desktops">台式机</Link> </li> <li> <Link to="laptops">笔记本电脑</Link> </li> </ul> </nav> <Outlet /> </div>);export const Desktops = () => <h3>台式电脑页面</h3>;export const Laptops = () => <h3>笔记本电脑页面</h3>;
刷新您的应用程序(如果开发服务器正在运行,此操作应自动完成),然后点击“分类目录”链接。现在您应该可以看到两个新的菜单项(台式机和笔记本电脑),并且点击其中任何一个将在原始的分类目录页面中显示一个新页面。
那么我们刚才做了什么呢?
在App.js
中,我们将/categories
路由更改为以下样子:
<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如何允许通过其路由配置进行组合。
通过将<Route>
组件嵌套在/categories
的<Route>
中,我们能够创建一个更结构化的URL和UI层次结构。这样,当用户导航到/categories/desktops
或/categories/laptops
时,将在<Categories>
组件内呈现相应的<Desktops>
或<Laptops>
组件,展示出路由和组件之间明确的父子关系。
注意:嵌套路由的路径会自动由其祖先路径与自身路径连接而成。
我们还修改了我们的<Categories>
组件以包含一个<Outlet>
:
export const Categories = () => ( <div> <h2>分类目录</h2> ... <Outlet /> </div>);
<Outlet>
放置在父路由元素中,用于呈现其子路由元素。这允许在渲染子路由时显示嵌套UI。
这种组合的方法使得路由配置更具声明性和易于理解,与React的基于组件的架构很好地契合。
使用Hooks访问路由属性
在早期版本中,某些属性会隐式地传递给组件。例如:
const Home = (props) => { console.log(props); return ( <h2>Home</h2> );};
上面的代码将输出以下内容:
{ history: { ... }, location: { ... }, match: { ... }}
在React Router 6中,传递路由属性的方式已经改变为提供一种更明确和基于hooks的方法。路由属性history
、location
和match
不再隐式地传递给组件。相反,提供了一组hooks来访问这些信息。
例如,要访问location
对象,您可以使用useLocation hook。 useMatch hook返回给定路径的路由匹配数据。 history
对象不再明确显示,而是useNavigate hook将返回一个函数,让您以编程方式进行导航。
还有许多其他的hooks可以探索,而不是在此处列出它们,我建议您查看官方文档,其中可以在左侧的侧边栏找到可用的hooks。
接下来,让我们更详细地介绍其中一个hooks并使我们之前的示例更加动态。
嵌套动态路由
首先,在App.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>
组件移动到自己的文件中:
// src/App.js...import Products from './Products';const Home = () => ( ... );export default function App() { ... }
最后,创建一个Products.js
文件并添加以下代码:
// src/Products.jsimport { Route, Routes, Link, useParams } from 'react-router-dom';const Item = () => { const { name } = useParams(); return ( <div> <h3>{name}</h3> <p>{name}的产品详情</p> </div> );};const Products = () => ( <div> <h2>产品</h2> <p>浏览个别产品。</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;
我们在顶部声明了一个<Item>
组件,然后将<Route>
添加到它。路由的路径设置为:name
,它将匹配其父路由之后的任何路径段,并将该路径段作为名为name
的参数传递给<Item>
组件。
在<Item>
组件的内部,我们使用了useParams hook。它会返回一个键/值对的对象,其中包含当前URL中的动态参数。如果我们在路由/products/laptops
中将其记录到控制台,我们会看到:
Object { "*": "laptops", name: "laptops" }
然后我们可以使用对象解构直接获取此参数,然后在<h3>
标签中渲染它。
试一试吧!正如你所见,<Item>
组件会捕捉到你在导航栏中声明的任何链接,并动态创建页面。
你也可以尝试添加一些更多的菜单项:
<li> <Link to="cyberpowerpc-gamer-xtreme">CyberPowerPC Gamer Xtreme</Link></li>
我们的应用将考虑到这些新页面。
通过捕捉URL的动态片段并将其作为组件内的参数使用,这种方法可根据URL结构实现更灵活的路由和组件渲染。
让我们在下一节继续深入。
带路径参数的嵌套路由
在真实的应用中,路由器必须处理数据并动态显示它。假设我们有一些由API返回的产品数据,格式如下:
const productData = [ { id: 1, name: "Dell OptiPlex 3090", description: "Dell OptiPlex 3090是一款紧凑的台式电脑,提供多种功能,满足您的业务需求。", status: "可用", }, { id: 2, name: "Lenovo ThinkPad X1 Carbon", description: "Lenovo ThinkPad X1 Carbon采用时尚耐用的设计,是适合在外工作的专业人士的高性能笔记本电脑。", status: "缺货", }, { id: 3, name: "CyberPowerPC Gamer Xtreme", description: "CyberPowerPC Gamer Xtreme是一款高性能游戏台式电脑,具有强大的处理和图形能力,为流畅的游戏体验提供支持。", status: "可用", }, { id: 4, name: "Apple MacBook Air", description: "Apple MacBook Air是一款轻薄便携的笔记本电脑,拥有高分辨率的Retina显示屏和强大的处理能力。", status: "缺货", },];
假设我们需要以下路径的路由:
/products
:应显示产品列表。/products/:productId
:如果存在具有:productId
的产品,应显示产品数据;否则,应显示错误消息。
请用以下内容替换Products.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>浏览各个产品。</p> <ul>{linkList}</ul> <Routes> <Route path=":productId" element={<Product data={productData} />} /> <Route index element={<p>请选择一个产品。</p>} /> </Routes> </div> );};export default Products;
在组件内部,我们使用每个产品的id
属性构建了一个<Link>
组件列表。我们将其存储在linkList
变量中,然后将其渲染到页面上。
下面是两个 <Route>
组件。第一个组件有一个值为 :productId
的 path
属性,这是一个路由参数(如前所述)。这允许我们在此段获取并使用 URL 中的值作为 productId
。此 <Route>
组件的 element
属性设置为渲染一个 <Product>
组件,并将 productData
数组作为 prop 传递给它。每当 URL 与模式匹配时,将渲染此 <Product>
组件,并从 URL 中捕获相应的 productId
。
第二个 <Route>
组件使用 index prop 在 URL 完全匹配基本路径时渲染文本 “请选择一个产品”。index
prop 表示此路由是此 <Routes>
设置中的基本或 “索引” 路由。因此,当 URL 与基本路径(即 /products
)匹配时,将显示此消息。
现在,这是我们上面提到的 <Product>
组件的代码。您需要在 src/Product.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>抱歉。产品不存在。</h2> )} </div> );};export default Product;
在这里,我们使用了 useParams
hook 来访问 URL 路径的动态部分作为键/值对。同样,我们使用解构来获取我们感兴趣的数据(productId
)。
在 data
数组上使用 find
方法来搜索并返回第一个其 id
属性与从 URL 参数获取的 productId
匹配的元素。
现在,当您在浏览器中访问该应用并选择 Products,您将看到一个子菜单被渲染,该子菜单反过来显示产品数据。
在继续之前,请随意尝试一下演示。确保一切正常并且您理解代码中正在发生的事情。
保护路由
对于许多现代 Web 应用程序来说,确保只有登录的用户可以访问站点的某些部分是一个常见的要求。在接下来的部分中,我们将看看如何实现受保护的路由,以便如果有人尝试访问 /admin
,他们将需要登录。
但是,我们首先需要介绍一些 React Router 的方面。
在 React Router v6 中以编程方式导航
在版本 6 中,通过 useNavigate
hook 来以编程方式重定向到一个新位置。此 hook 提供了一个函数,可用于以编程方式导航到不同的路由。它可以接受一个对象作为第二个参数,用于指定各种选项。例如:
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
,然后添加以下内容:
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>
组件并将其包裹在某个组件周围时,children
属性就是被保护的内容。例如:
<PrivateRoute> <Admin /> <-- children</PrivateRoute>
在组件体内,使用useNavigate
和useLocation
钩子函数分别获取navigate
函数和当前location
对象。如果用户未经过身份验证,即!fakeAuth.isAuthenticated
为真,就会调用navigate
函数将用户重定向到/login
路径,就像前一节描述的那样。
组件的返回语句再次检查身份验证状态。如果用户已经通过身份验证,就会渲染其子组件。如果用户未经身份验证,则返回null
,不渲染任何内容。
还要注意的是,我们将对navigate
的调用包装在React的useEffect钩子函数中。这是因为在组件体内直接调用navigate
函数会导致在渲染过程中进行状态更新。将其包装在useEffect
钩子函数中可以确保在组件渲染后再调用它。
重要的安全提示
在真实的应用程序中,您需要在服务器上验证对受保护资源的任何请求。让我再说一遍…
在真实的应用程序中,您需要在服务器上验证对受保护资源的任何请求。
这是因为在客户端运行的任何东西都有可能被逆向工程和篡改。例如,在上述代码中,一个人只需打开React的开发工具并将isAuthenticated
的值更改为true
,就可以访问受保护区域。
在React应用程序中进行身份验证值得有一个专门的教程,但一种实现它的方法是使用JSON Web Tokens。例如,您可以在服务器上拥有一个端点,该端点接受用户名和密码组合。当它接收到这些组合(通过Ajax),它会检查凭据是否有效。如果有效,它会响应一个JWT,React应用程序会保存它(例如,在sessionStorage
中),如果无效,它会向客户端发送一个401未授权
的响应。
假设登录成功,客户端随后会将JWT作为头信息与任何请求受保护资源的请求一起发送。然后服务器会在发送响应之前验证它。
在存储密码时,服务器不会以明文形式存储密码。相反,它会对其进行加密 – 例如使用 bcryptjs。
实现保护的路由
现在让我们实现我们的保护路由。将 App.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>主页</h2> <p>欢迎来到我们的主页!</p> </div>);const Admin = () => ( <div> <h2>欢迎管理员!</h2> </div>);export default function App() { return ( <div> <nav> <ul> <li> <Link to="/">主页</Link> </li> <li> <Link to="/categories">分类</Link> </li> <li> <Link to="/products">产品</Link> </li> <li> <Link to="/admin">管理员区域</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>
组件,并将 <PrivateRoute>
包含在 <Routes>
组件中。如前所述,这个自定义路由在用户登录时渲染 <Admin>
组件。否则,用户将被重定向到 /login
。
最后,创建 Login.js
并添加以下代码:
// src/Login.jsimport { 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>您必须登录才能查看页面 {from.pathname}</p> <button onClick={login}>登录</button> </div> );}/* 一个假的身份验证函数 */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
。这会导致组件重新渲染并将用户重定向。
工作演示
让我们把拼图的碎片拼在一起,好吗?这是我们使用React router构建的应用程序的最终演示。
https://codesandbox.io/embed/react-router-demo-wpwmcv
React Router版本6.4
在我们结束之前,我们应该提到React Router v6.4的发布。尽管看起来只是一个不显眼的小版本更新,但这个版本引入了一些划时代的新功能。例如,它现在包含了从Remix中引入的数据加载和变更API,这为保持UI与数据同步引入了全新的范例。
从版本6.4开始,你可以为每个路由定义一个loader
函数,该函数负责获取该路由所需的数据。在组件内部,你可以使用useLoaderData
hook来访问由loader函数加载的数据。当用户导航到一个路由时,React Router会自动调用相关的loader函数,获取数据,并通过useLoaderData
hook将数据传递给组件,而无需使用useEffect
。这促使了数据获取与路由直接相关的模式。
还有一个新的<Form>
组件,它阻止浏览器将请求发送到服务器,并发送到你的路由的action
。完成操作后,React Router会自动重新验证页面上的数据,这意味着所有的useLoaderData
hooks都会更新,UI会自动与数据保持同步。
要使用这些新的API,你需要使用新的<RouterProvider />
组件。它接受一个router
属性,该属性是使用新的createBrowserRouter函数创建的。
在本文的范围之外详细讨论所有这些变化,但如果你想了解更多,我鼓励你跟随React Router官方教程。
摘要
正如你在本文中所看到的,React Router是一个强大的库,为React应用程序提供了更好的声明式路由功能。本文撰写时,React Router的当前版本是v6.18,并且该库自v5以来经历了重大变化。这部分是由于同一作者编写的全栈Web框架Remix的影响。
在本教程中,我们学到了:
- 如何设置和安装React Router
- 路由的基础知识以及一些必要的组件,如
<Routes>
、<Route>
和<Link>
- 如何创建用于导航和嵌套路由的最小路由器
- 如何使用路径参数构建动态路由
- 如何使用React Router的hooks和较新的路由渲染模式
最后,我们在创建受保护路由的最终演示中学习了一些高级路由技术。
常见问题
React Router v6中的路由是如何工作的?
这个版本引入了一种新的路由语法,使用<Routes>
和<Route>
组件。<Routes>
组件包裹着各个<Route>
组件,它们指定了路径和当路径与URL匹配时要渲染的元素。
如何设置嵌套路由?
嵌套路由是通过在 JSX 代码中将<Route>
组件放置在其他<Route>
组件内部来创建的。这样,嵌套的<Route>
组件就能自然地反映出它们所代表的 URL 的嵌套结构。
如何将用户重定向到另一个页面?
您可以使用useNavigate
钩子来以编程方式将用户导航到另一个页面。例如,const navigate = useNavigate();
然后navigate('/path');
以重定向到所需的路径。
如何将属性传递给组件?
在 v6 中,您可以通过将它们包含在<Route>
组件的元素属性中来向组件传递属性,例如:<Route path="/path" element={<Component prop={value} />} />
。
如何在 React Router v6 中访问 URL 参数?
可以使用useParams
钩子来访问 URL 参数。例如,如果路由被定义为<Route path=":id" element={<Component />} />
,您可以使用const { id } = useParams();
来访问<Component />
内的 id 参数。
React Router v6.4 有哪些新功能?
6.4 版本引入了许多受 Remix 启发的新功能,例如数据加载器和createBrowserRouter
,旨在改进数据获取和提交。您可以在这里找到新功能的详尽列表。
如何从 React Router v5 迁移到 v6?
迁移涉及将路由配置更新为新的<Routes>
和<Route>
组件语法,将钩子和其他 API 方法更新为它们的 v6 对应物,并处理应用程序路由逻辑中的任何变更。您可以在这里找到官方指南。
Leave a Reply