Joel 2024-07-19
学习如何使用 React Router 在 React 中实现路由,以及路由的各个方面及其在 React Router 中的处理方式。
React 是一个开源的前端 JavaScript 框架,允许开发者使用 UI 组件和单页应用程序(SPA)来构建网站和用户界面。在开发这些应用时,路由是我们始终希望实现的最重要功能之一。
路由会根据用户的操作或请求将用户重定向到不同的页面。在 React 路由中,你会使用一个名为 React Router 的外部库,如果你不了解其工作原理,配置起来可能会有些困难。
在本文中,我们将向你展示如何使用 React Router 在 React 中实现路由。你将学习路由的各个方面以及 React Router 如何处理它们,例如动态路由、编程式导航、无匹配路由等。
入门
为了充分理解并跟随本指南,我们将创建一个能够清晰展示所有导航方面的应用,并配有合适的用例。我们将创建/使用一个从 Hygraph 通过 GraphQL 获取数据的鸡尾酒(cocktails)应用。这个应用可以通过 此在线链接 访问,并使用了本指南涵盖的所有路由特性。
下图展示了 Hygraph 中用于 React 动态路由的 'Cocktail' 内容模型的 schema,其中包含用于动态路由的 'slug' 字段:
前提条件
要顺利跟随本指南进行编码,你需要具备以下条件:
- 对 HTML、CSS 和 JavaScript 有基本了解
- 具备一些 React 使用经验或知识
- 你的机器上已安装 Node 和 npm 或 yarn
- 已使用 Create React App 创建了一个 React 应用
在我们的应用中添加 React Router
如何安装 React Router
如前所述,React 使用一个外部库来处理路由;但在使用该库实现路由之前,我们必须先在项目中安装它。在终端(位于你的项目目录中)运行以下命令即可完成安装:
npm install react-router-dom
成功安装该包后,我们就可以为项目设置并配置 React Router 了。
如何设置 React Router
要配置 React Router,请导航到作为根文件的 index.js,从 react-router-dom 包中导入 BrowserRouter,并将其包裹在 App 组件周围,如下所示:
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
如何在 React 中配置路由
现在我们已经成功安装并导入了 React Router;下一步是使用 React Router 实现路由。第一步是配置我们所有的路由(即我们希望导航的所有页面/组件)。
我们将首先创建这些组件。在本例中,我们创建三个页面:首页(Home)、关于页(About)和产品页(Products)。一旦这些页面正确配置完毕,我们就可以在作为 React 应用基础的 App.js 文件中设置和配置路由:
// App.js
import { Routes, Route } from 'react-router-dom';
import Home from './Pages/Home';
import About from './Pages/About';
import Products from './Pages/Products';
const App = () => {
return (
<>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route path="/about" element={<About />} />
</Routes>
</>
);
};
export default App;
在上面的代码中,我们可以看到我们从 react-router-dom 导入了 Routes 和 Route 组件,然后使用它们声明了我们想要的路由。所有 Route 都被包裹在 <Routes> 标签内,每个 Route 有两个主要属性:
- path:顾名思义,这指定了用户访问指定组件所需采取的路径。例如,当我们把路径设为
/about时,用户在 URL 后加上/about就会导航到该页面。 - element:这包含我们希望指定路径加载的组件。这一点很容易理解,但请记住要导入此处使用的所有组件,否则会出现错误。
编辑者注:我们创建了一个名为
Pages的文件夹,用于将所有页面组件与普通组件分开存放。
当我们打开浏览器并通过 URL 导航时,它将加载这些页面上的内容。
添加导航栏
现在让我们创建一个标准的导航栏组件,用于在应用内部进行导航。
首先,创建 Navbar 组件:
// component/NavBar.js
import { NavLink } from "react-router-dom";
const NavBar = () => {
return (
<nav>
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/about">About</NavLink>
</li>
<li>
<NavLink to="/products">Products</NavLink>
</li>
</ul>
</nav>
);
};
export default NavBar;
react-router-dom 提供的 NavLink 组件是一个特殊组件,它通过 to 属性帮助你在不同路由之间导航。NavLink 组件还能识别当前路由是否“激活”,并自动为链接添加默认的 active 类。我们可以利用这个类在 CSS 中为激活的链接定义样式,如下所示:
/* index.css */
ul li a {
color: #000;
}
ul li a:hover {
color: #00a8ff;
}
ul li a.active {
color: #00a8ff;
}
此外,我们也可以不使用默认的 active 类,而是分配自定义类。NavLink 组件还为我们提供了 isActive 等属性,可以这样使用:
<li>
<NavLink
to="/"
className={({ isActive }) => {
return isActive ? "active-link" : "";
}}
>
Home
</NavLink>
</li>
最后,让我们在 App 中使用 Navbar 组件:
// App.js
import NavBar from "./Components/Navbar";
import { Routes, Route } from "react-router-dom";
const App = () => {
return (
<>
<NavBar />
<Routes>
{/* ... */}
</Routes>
</>
);
};
export default App;
如何修复“未找到路由”错误
在路由过程中,可能会出现用户访问未配置路由或不存在路由的情况;此时 React 不会在屏幕上显示任何内容,只会显示一条警告信息:“No routes matched location.”(未匹配到任何路由)。
我们可以通过配置一个新路由,在用户导航到未配置路由时返回一个特定组件来解决这个问题,如下所示:
// App.js
import { Routes, Route } from 'react-router-dom';
import NoMatch from './Components/NoMatch';
const App = () => {
return (
<>
<Routes>
{/* ... */}
<Route path="*" element={<NoMatch />} />
</Routes>
</>
);
};
export default App;
在上述代码中,我们创建了一个路径为 * 的路由,用于捕获所有未配置的路径,并将其指向关联的组件。
编辑者注:我们创建了一个名为
NoMatch.js的组件,但你可以随意命名。该组件用于在屏幕上显示“404 页面未找到”等内容,让用户知道自己进入了错误页面。我们还可以添加一个按钮,将用户引导至其他页面或返回上一页,这就引出了编程式导航。
如何在 React 中实现编程式导航
编程式导航是指在路由上发生某个操作(如登录、注册、订单成功,或点击返回按钮)时,对用户进行导航/重定向。
首先,我们来看如何在某个操作(例如点击按钮)发生时重定向到某个页面。我们通过添加 onClick 事件来实现这一点,但首先必须在 App.js 文件中创建该路由。之后,我们可以从 react-router-dom 导入 useNavigate Hook 并用于编程式导航,如下所示:
// Products.js
import { useNavigate } from 'react-router-dom';
const Products = () => {
const navigate = useNavigate();
return (
<div className="container">
<div className="title">
<h1>Order Product CockTails</h1>
</div>
<button className="btn" onClick={() => navigate('order-summary')}>
Place Order
</button>
</div>
);
};
export default Products;
编辑者注:我们已经创建了一个路径为
order-summary的路由,因此当点击此按钮时,用户将自动导航到与此路由关联的orderSummary组件。
你也可以使用以下方式实现“返回上一页”功能:
<button className="btn" onClick={() => navigate(-1)}>
Go Back
</button>
请确保你已经像之前那样导入并实例化了该 Hook,否则此功能将无法工作。
如何使用 React Router 实现动态路由
我们之前在 pages 文件夹中创建了三个文件来实现路由,其中之一是 Products 组件,我们将用 Hygraph 的内容填充它。我们在 Hygraph 中创建了一个用于接收鸡尾酒详情的 schema,如下图所示:
然后我们填充了鸡尾酒的具体信息。现在我们将使用 GraphQL 获取这些数据,以便在 React 项目中使用。产品页面如下所示:
// Products.js
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { getAllCocktails } from "../api";
import ProductCard from "../Components/ProductCard";
const Products = () => {
const [products, setProducts] = useState([]);
const navigate = useNavigate();
useEffect(() => {
const fetchProducts = async () => {
const { cocktails } = await getAllCocktails();
setProducts(cocktails);
};
fetchProducts();
}, []);
return (
<div className="container">
<button className="btn" onClick={() => navigate(-1)}>
Go Back
</button>
<div className="title">
<h1>CockTails</h1>
</div>
<div className="cocktails-container">
{products.map((product) => (
<ProductCard product={product} />
))}
</div>
</div>
);
};
export default Products;
// components/ProductCard.js
import { Link } from "react-router-dom";
const ProductCard = ({ product }) => {
if (!product) {
return null;
}
return (
<div key={product.id} className="cocktail-card">
<img src={product.image.url} alt="" className="cocktail-img" />
<div className="cocktail-info">
<div className="content-text">
<h2 className="cocktail-name">{product.name}</h2>
<span className="info">{product.info}</span>
</div>
<Link to={`/products/${product.slug}`}>
<div className="btn">View Details</div>
</Link>
</div>
</div>
);
};
export default ProductCard;
编辑者注:你可以在此处了解更多关于 React 和 Hygraph 的内容。
在上述代码中,我们从 Hygraph 获取了内容;如果你已经创建了自己的 schema,只需更改 Endpoint URL,如果 schema 名称不同,也可能需要调整名称。
编辑者注:我们在每个鸡尾酒卡片上添加了一个按钮,用户点击后可以查看每款鸡尾酒的更多详情。但由于鸡尾酒种类可能很多(超过 5 种),为每款单独创建组件会非常繁琐。这时就轮到动态路由发挥作用了。
我们添加了一个 Link,并使用字符串插值将每个产品的 slug 动态附加到路径中,这样我们就可以获取 slug 并用它来获取要显示的数据。
现在让我们开始实现动态路由。
第一步是创建我们要动态渲染的组件。为此,我们将创建一个 ProductDetails.js 文件,在其中根据 URL 传递的 slug 动态获取每个产品的详情。目前我们可以先在组件中放置一些占位内容,如下所示:
// ProductDetails.js
const ProductDetails = () => {
return (
<div className="container">
<h1>Products Details Page</h1>
</div>
);
};
export default ProductDetails;
现在我们可以在 App.js 文件中创建一个路由来处理动态路由,如下所示:
// App.js
import { Routes, Route } from 'react-router-dom';
// ...
import ProductDetails from './Pages/ProductDetails';
const App = () => {
return (
<>
<Routes>
{/* ... */}
<Route path="/products/:slug" element={<ProductDetails />} />
</Routes>
</>
);
};
export default App;
编辑者注:我们使用了
slug,它可以是任意名称,但只要路径模式相同(例如http://localhost:3000/products/cocktail),该路由就会匹配任意值并显示对应组件。
到目前为止,我们已经完成了动态路由的第一部分。现在我们必须获取通过 URL 传递的参数,以便动态查询特定鸡尾酒的数据。这将通过 urlParams 来实现。
如何使用 URL 参数处理动态路由
我们将把 useParams Hook 导入到 ProductDetails 组件中,以便获取 URL 参数,然后使用该参数通过 GraphQL 从 Hygraph 查询数据。
// ProductDetails.js
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { getProductBySlug } from "../api";
const ProductDetails = () => {
const [product, setProduct] = useState([]);
const navigate = useNavigate();
// 从路由参数中获取 slug
const { slug } = useParams();
useEffect(() => {
const fetchProduct = async () => {
const { cocktail } = await getProductBySlug(slug);
setProduct(cocktail);
};
fetchProduct();
}, [slug]);
return (
<div className="container">
{/* ...Product Details template */}
</div>
);
};
export default ProductDetails;
至此,我们已成功获取了通过 URL 传递的参数。接下来,让我们利用这个 slug 通过 GraphQL 从 Hygraph 获取数据:
至此,我们已成功实现动态路由。
如何使用 React Router 实现懒加载
我们已经了解了如何创建路由并在 React Router 中实现路由;现在让我们看看如何使用 React Router 实现路由的懒加载。
懒加载是一种技术,它使得首页不需要的组件不会在初始加载时加载,而是在用户导航到该页面时才加载。这可以让我们的应用加载速度更快,而不是等待整个应用一次性加载完毕。这有助于提升性能,从而带来更好的用户体验。
要实现懒加载,只需前往 App.js,用 Suspense 组件包裹我们的路由,并提供一个 fallback 属性,在组件加载期间显示相应内容:
// App.js
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import NavBar from './Components/NavBar';
const Home = lazy(() => import('./Pages/Home'));
const About = lazy(() => import('./Pages/About'));
const Products = lazy(() => import('./Pages/Products'));
const ProductDetails = lazy(() => import('./Pages/ProductDetails'));
const NoMatch = lazy(() => import('./Components/NoMatch'));
const App = () => {
return (
<>
<NavBar />
<Suspense fallback={<div className="container">Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:slug" element={<ProductDetails />} />
<Route path="*" element={<NoMatch />} />
</Routes>
</Suspense>
</>
);
};
export default App;
编辑者注:我们用
Suspense组件包裹了路由,需要注意的是,fallback属性可以包含一个组件。
结论
在本指南中,我们学习了路由以及如何在 React 应用中实现它。必须认识到,React Router 正是使我们能够在不重新加载应用的情况下实现单页路由的关键工具。