Web 应用中的路由允许用户无缝导航,确保用户可以轻松地在应用内访问不同的页面和资源。因此,路由如果实施得当,可以提供直观的用户体验,增强互动性。默认情况下,React 没有内置的路由功能,这就是为什么开发者依赖 React Router - 一个用于 React 应用的第三方客户端路由库的原因。
React Router 采用声明式方法定义路由,这意味着它允许您声明应用的哪个组件将显示在特定路由中。这样,您可以定义直观且逻辑的路由,可以修改这些路由而不会影响应用的其他部分。
从本质上讲,React 路由允许单页应用 (SPA) 模拟多页体验 - 而不必进行完整的页面重新加载。这使您能够构建复杂的应用,并减少服务器负载,不像多页应用那样。当用户导航到特定路由时,React Router 会动态加载并渲染相应的组件,而无需服务器为每个请求生成整个页面。这提供了一种流畅的用户体验,因为只有 UI 的必要部分会更新。
传统的 多页 Web 应用使用基于服务器的路由,通常包含大量的视图文件。这意味着,用户每次请求新页面时,都必须将请求发送到服务器。然后,服务器会通过生成并返回所请求的页面来响应。当用户与页面进行交互时,浏览器会再次向服务器发送请求,从而导致完整的页面重新加载,这会对用户体验产生负面影响。
另一方面,SPA 使用客户端路由,通常加载单个 HTML 页面。当用户导航到不同的路由时,React Router 会拦截 URL 并更新浏览器历史记录,而无需向服务器发送请求。然后,根据路由,页面会使用相应的组件进行动态更新。当用户与页面交互时,只有必要的组件会更新,而无需进行完整的页面重新加载。这会带来流畅的用户体验和更快的页面加载时间。
此外,得益于 React Router 的客户端路由,SPA 拥有更低的服务器负载。与服务器的唯一通信是通过 API 来获取数据。这使后端与前端分离,从而可以创建更模块化且更灵活的代码库。此外,使用分离的架构,开发者可以更容易地进行协作,因为他们可以同时在后端和前端进行工作。
React Router 提供组件和钩子来管理用户的导航历史记录。这些核心组件包括
BrowserRouter 组件是 React Router 的核心,负责将您的 UI 与 URL 同步。这意味着,对 URL 的任何更改都会导致渲染相应的组件,并且任何导航操作(例如,单击链接或以编程方式导航)都会相应地更新 URL。
在幕后,该组件使用 HTML5 历史记录 API - pushState、replaceState 和 popState - 来监控和操作应用的浏览历史记录。通常,您将整个应用包装在 BrowserRouter 组件中,以便 React Router 可以访问和控制您的应用的位置。
import ReactDOM from 'react-dom';
import * as React from 'react';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('app')
);
在本示例中,BrowserRouter 组件包装了整个应用,使 React Router 能够处理您的应用的路由。
除了 BrowserRouter,您还可以使用 MemoryRouter、HashRouter 或 StaticRouter。MemoryRouter 在内部存储 URL 的历史记录,并且不读取或写入地址栏。这在您不需要与浏览器的历史记录栈进行交互的情况下非常有用,例如,测试环境。HashRouter 使用 URL 的哈希部分(即 # 符号之后的的部分)来将 UI 与 URL 同步。这在您需要支持不支持 HTML5 历史记录 API 的旧版浏览器的情况下非常有用。StaticRouter 对于静态网站生成特别有用,在这种情况下,路由器不需要任何导航,而是根据提供的 location 属性进行渲染。
Routes 组件用于对应用的所有路由进行分组。每个 Route 用于指定一个路径和当 URL 与该路径匹配时应渲染的 React 组件。例如,如果您想在用户导航到 “/” 时渲染 Home 组件。您可以像这样定义您的路由
import { Routes, Route } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Contact from './Contact';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
);
}
export default App;
在上面的示例中,当 URL 路径为 “/” 时,将渲染 Home 组件。同样,当 URL 路径为 “/about” 或 “/contact” 时,将分别渲染 About 或 Contact 组件。
您还可以嵌套路由以创建分层的 URL 结构,从而更轻松地管理复杂的导航和布局。例如,如果您有一个仪表盘路由,您可以在父路由中嵌套其他与仪表盘相关的路由。
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<DashboardHome />} />
<Route path="settings" element={<DashboardSettings />} />
<Route path="profile" element={<DashboardProfile />} />
</Route>
Link 组件用于在您的应用中创建导航链接。它类似于锚点 HTML 标签,但在单击时不会导致默认的重新加载行为。
import { Link } from 'react-router-dom';
function Navigation() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
);
}
NavLink 的工作方式与 Link 相同,但它添加了一个 active 样式属性来突出显示活动链接。
import { NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
<NavLink to="/" activeClassName="active">Home</NavLink>
<NavLink to="/about" activeClassName="active">About</NavLink>
<NavLink to="/contact" activeClassName="active">Contact</NavLink>
</nav>
);
}
useNavigate 钩子允许用户以编程方式在路由之间导航。它提供了一个函数,您可以调用该函数来更改当前 URL,这反过来会根据路由配置更新显示的组件。当您想要在完成操作后将用户重定向到另一个页面时,它特别有用,例如,在提交表单后。
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleSubmit = () => {
// Perform login logic
navigate('/dashboard');
};
return <button onClick={handleSubmit}>Login</button>;
}
或者,您还可以使用 Navigate 来采用更声明式的导航方法,特别是在组件的渲染方法中根据状态或道具有条件地导航时。
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated }) {
if (isAuthenticated) {
return <Navigate to="/dashboard" />;
} else {
return <Navigate to="/login" />;
}
}
useParams 钩子允许用户访问路由的动态参数。当您需要根据 URL 参数(例如,ID)来获取或显示数据时,这特别有用。本质上,该钩子会返回一个键值对对象,其中键是参数名称,值是来自 URL 的参数值。例如,如果您要根据 id 来获取产品的详细信息,以下是如何实现它
import { Routes, Route } from 'react-router-dom';
import Home from './Home';
import Products from './Products';
import ProductDetail from './ProductDetail';
import About from './About';
function App() {
return (
<Routes>
<Route path=”/” element={<Home />} />
<Route path="products" element={<Products />}>
<Route path=":id" element={<ProductDetail />} />
</Route>
<Route path="about" element={<About />} />
</Routes>
);
}
export default App;
请注意,在上面的示例中,ProductDetail 路由嵌套在 Products 路由中,并使用 URL 参数 :id 来表示单个产品 ID。
在 Products 组件中,您可以显示产品的列表,供用户导航到产品的详细信息页面。Outlet 组件会渲染父产品路由的子路由。
import { Outlet, Link } from 'react-router-dom';
function Products() {
return (
<div>
<h1>Products</h1>
<ul>
<li><Link to="1">Product 1</Link></li>
<li><Link to="2">Product 2</Link></li>
<li><Link to="3">Product 3</Link></li>
</ul>
<Outlet />
</div>
);
}
export default Products;
在 ProductDetails 组件中,您可以使用 useParams 钩子从 URL 中提取 id 参数,并使用它来获取和显示与所选产品相关的详细信息。
import { useParams } from 'react-router-dom';
function ProductDetail() {
const { id } = useParams();
return (
<div>
<h2>Product Detail</h2>
<p>Displaying details for product ID: {id}</p>
{/* Additional logic to fetch and display product details can go here */}
</div>
);
}
export default ProductDetail;
在查看了 React Router 的基本组件和钩子后,了解一些有关有效路由的最佳实践非常重要。这些实践不仅可以确保安全高效的导航体验,还可以提高应用的可维护性、可扩展性和性能。
理想情况下,您的应用应组织成目录,所有相关文件都存储在一个目录中。因此,您的所有路由定义都应放在一个目录中,这将使管理和修改路由变得更加容易。最重要的是,您的路由应分解成小的、可复用的组件,以进一步提高单个路由的可维护性。
现代应用中的 JavaScript 包很容易变得很大,这会导致应用的整体性能下降。这就是延迟加载发挥作用的地方,它提供了一种优化加载时间和提高应用性能的解决方案。它的工作原理是允许您将代码拆分成更小的块,这些块可以按需加载。这样,当用户访问您的网站时,他们不必立即下载所有代码,尤其是在他们没有访问过且目前不需要的路由的情况下。
要实现延迟加载,您需要使用 React.lazy。这是一个内置函数,它允许您动态地导入组件,只有在需要时才会加载。当与 React Router 一起使用时,它允许您推迟加载组件,直到用户访问需要该特定组件的路由为止。
该函数以导入作为参数,并返回一个组件。以下是如何实现它
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));
const Products = React.lazy(() => import('./Products'));
const ProductDetail = React.lazy(() => import('./ProductDetail'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="products" element={<Products />}>
<Route path=":id" element={<ProductDetail />} />
</Route>
</Routes>
</Suspense>
</Router>
);
}
export default App;
当用户访问主页时,只会加载 Home 组件。请注意,Suspense 组件用于包装延迟加载的路由。它接收一个 fallback 道具,该道具接受您想要渲染的 React 组件,同时正在加载延迟加载的路由。理想情况下,这应该是一个加载动画或加载消息,以向用户显示应用正在获取所需的路由。
当用户在您的应用中导航时,他们可能会遇到错误,例如尝试访问不存在的路由。因此,有效的错误处理对于确保您的应用能够优雅地处理此类错误至关重要,从而保证无缝的用户体验。
处理路由错误的最佳方法之一是实施一个回退路由,以捕获与未定义路径相关的错误。本质上,这被称为通配路由,它会显示一个错误页面,并且还可以包含一个将用户带回主页的导航按钮。要实现它,您所要做的就是定义一个路径为 “*” 的路由,该路由会渲染一个 NotFound 元素。
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="products" element={<Products />} />
<Route path="*" element={<NotFound />} />
</Routes>
除了处理与不存在的路由相关的错误之外,您还可以考虑处理应用本身发生的错误。这可以通过使用来自 react-error-boundary 库的 ErrorBoundary 组件来实现。该组件接收一个回退组件,如果发生错误,该组件将显示。
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { ErrorBoundary } from 'react-error-boundary';
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));
const Products = React.lazy(() => import('./Products'));
const ProductDetail = React.lazy(() => import('./ProductDetail'));
const NotFound = React.lazy(() => import('./NotFound'));
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function App() {
return (
<Router>
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// reset the state of your app so the error doesn't happen again
}}
>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="products" element={<Products />}>
<Route path=":id" element={<ProductDetail />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</ErrorBoundary>
</Router>
);
}
export default App;
这种设置可确保您构建一个稳健的应用,可以优雅地处理加载状态和路由错误。
React Router 是 React 生态系统中最受欢迎的路由库,主要是因为它拥有丰富的文档、易于设置以及庞大的用户社区。它还提供了广泛的功能,使其成为复杂应用的理想选择。但是,还有其他 React Router 的替代方案可以同样完成工作。它们包括
Tanstack Router 是一个功能强大的路由库,非常注重类型安全,使其成为 TypeScript 项目的绝佳选择。该库还与内置的数据获取支持很好地集成,这使您可以轻松地使数据失效、缓存和重新获取数据。它还具有独特的先进功能,例如 search-param API,它允许您比使用 React Router 的 useParams 钩子更容易地管理 URL 查询参数。
Wouter 是一个轻量级的路由库,适用于 React 和 Preact 应用。它旨在实现简单和简约,因此包体积很小,但具有 React Router 的所有功能。该库非常适合那些优先考虑性能的应用,因为它功能简洁。它的一些高级功能包括代码拆分、路由过渡和路由守卫。
Reach Router 与 Wouter 类似,它简单且轻量级。它的设计考虑了可访问性,确保路由更改会由屏幕阅读器宣布。唯一的缺点是,与 React Router 不同,Reach Router 缺乏广泛的集成生态系统。
问:什么是 React Router,如何使用它?
A: React Router 是一个第三方路由库,它提供组件、钩子和实用函数来实现现代路由策略。 要开始使用,您需要使用您喜欢的包管理器安装库 - npm install react-router-dom 或 yarn add react-router-dom。
Q: 我可以将 React Router 与 React 之外的其他框架一起使用吗?
A: 不,React Router 是专门为 React 应用程序设计的。 对于其他前端框架(如 Vue 和 Angular),您将需要它们各自的库,例如 Vue Router 或 Angular Router。
Q: 如何在 React Router 中保护路由并管理用户访问权限?
A: 您可以使用路由守卫和重定向来保护路由并管理用户访问权限。 这可以通过创建一个高阶组件 (HOC) 或自定义组件来实现,该组件在渲染所需路由组件之前检查用户身份验证。 如果用户未经身份验证,您可以将他们重定向到登录页面或其他适当的路由。
React Router 是一个健壮的路由库,用于构建动态单页应用程序。 它对路由的声明式方法允许开发人员直接在他们的 JSX 中定义路由。 这有助于提高代码的可维护性和组织性。 它还拥有广泛的功能,包括嵌套路由、动态路由匹配和延迟加载,使其成为构建复杂 SPA 的理想选择。 虽然有其他替代 React 路由的方案,但 React Router 由于其出色的开发者体验、高级功能和丰富的文档,仍然是最受欢迎的选择。