[译] React Router v6:入门指南

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> 和我们将要处理的所有 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 -v
> 20.9.0

npm -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 基础知识

Router 组件

我们需要做的第一件事是用 <Router> 组件(由 React Router 提供)来包裹 <App> 组件。路由有多种类型,在我们的案例中,有两种路由值得考虑:

它们之间的主要区别体现在所创建的 URL 上:

// <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 改为如下所示:

// 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。

LinkRoute 组件

<Route> 组件是 React Rtouter 中最重要的组件。如果路由地址和当前 URL 路径匹配,它就会在界面渲染一些内容。正常情况下,<Route> 组件应该有一个名为 path 的属性,如果这个属性值与当前 URL 路径匹配,组件内容就会被渲染。

<Link> 组件则用于在页面之间导航。它类似于 HTML 锚点元素(<a>)。不过,使用锚链接会导致整个页面刷新,这是我们不希望看到的。因此,我们可以使用 <Link> 来导航到一个特定的 URL,并在不刷新的情况下重新渲染视图。

现在,我们的程序应该能正常运行了。删除项目 src 文件夹中除 index.jsApp.js 以外的所有文件,然后按如下步骤更新 App.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(其中...表示前面的代码保持不变):

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.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 链接。现在,你应该可以看到两个新的菜单项(DesktopsLaptops),点击其中任何一个都会在原 Categories 页面内显示一个新内容。

我们刚才做了什么?

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 如何通过路由配置实现路由组合。

通过在 /categories <Route> 中嵌套 <Route> 组件,我们可以创建结构更合理的 URL 和 UI 层次结构。这样,当用户导航到 /categories/desktops/categories/laptops 时,相应的 <Desktops><Laptops> 组件将在 <Categories> 组件中渲染,从而清晰地展示出各个路由和组件之间的父子关系。

注意:嵌套路由的路径是由其祖先的路径和自身的路径连接而成的。

我们还修改了 <Categories> 组件,使其包含一个 <Outlet />

export const Categories = () => (
  <div>
    <h2>Categories</h2>
    ...
    <Outlet />
  </div>
);

<Outlet> 位于父路由元素中,用于渲染其子路由元素。这样就可以在渲染子路由时显示嵌套的界面。

这种组合式方法使路由配置更具声明性,更易于理解,与 React 基于组件的架构非常吻合。

使用 Hook 访问路由属性

在之前版本中,某些属性是隐式传递给组件的。例如:

const Home = (props) => {
  console.log(props);
  return ( <h2>Home</h2> );
};

上述代码将输出如下内容:

{
  history: { ... },
  location: { ... },
  match: { ... }
}

在 React Router 第 6 版中,传递路由属性的方法发生了变化,提供了一种更明确的基于 Hook 的方法。路由属性 history、location 和 match 不再隐式传递给组件。相反,现在使用一组 Hook 来访问这些信息。

例如,要访问 location 对象,可以使用 useLocation HookuseMatch Hook 会返回匹配到的路径参数。history 对象不再显式使用,而是通过 useNavigate Hook 返回一个函数,让你以编程方式进行导航。

还有更多 Hook 值得探索,我不在此一一列举,而是建议你查看官方文档,在左侧边栏中可以找到可用的 Hook。

接下来,让我们更详细地了解其中一个 Hook,使我们之前的示例更具活力。

嵌套动态路由

首先,像这样更改 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.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 路径下,如果我们参数输出到控制台,我们会看到:

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:
      "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 的当前内容(确保复制了上面的产品数据):

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

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 提供了一个函数,可用于以编程方式导航到不同的路径。它可以接受一个对象作为第二个参数,用于指定各种选项。例如:

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> 组件被调用时包裹的受保护内容。例如:

<PrivateRoute>
  <Admin /> <-- children
</PrivateRoute>

在组件主体中,useNavigateuseLocation 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

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,并添加以下代码:

// 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 HookredirectToReferrer 属性初始化为 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 对应方法,以及处理应用中路由逻辑的任何破坏性更改。你可以在此处找到官方指南。

原文链接:https://juejin.cn/post/7317141855518834688 作者:晓得迷路了

(0)
上一篇 2023年12月28日 上午10:00
下一篇 2023年12月28日 上午10:10

相关推荐

发表回复

登录后才能评论