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-routerreact-router-domreact-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变化时修改其状态。这会触发应用程序的重新渲染,确保显示适当的用户界面。

接下来,我们有链接和路由。

LinkRoute组件

<Route>组件是React Router中最重要的组件。如果位置匹配当前路由路径,它会渲染一些用户界面。理想情况下,<Route>组件应该有一个名为path的属性,如果路径名称与当前位置匹配,则会被渲染。

另一方面,<Link>组件用于在页面之间导航。它类似于HTML的锚元素。然而,使用锚链接会导致完整页面刷新,这是我们不想要的。因此,我们可以使用<Link>导航到特定的URL,并在重新渲染视图时不刷新。

现在,我们已经介绍了使我们的应用程序工作所需的一切。从项目的src文件夹中删除除index.jsApp.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的方法。路由属性historylocationmatch不再隐式地传递给组件。相反,提供了一组hooks来访问这些信息。

例如,要访问location对象,您可以使用useLocation hookuseMatch 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> 组件。第一个组件有一个值为 :productIdpath 属性,这是一个路由参数(如前所述)。这允许我们在此段获取并使用 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 的方面。

在版本 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>

在组件体内,使用useNavigateuseLocation钩子函数分别获取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

Your email address will not be published. Required fields are marked *