React Router V6 重定向指南:深入理解 Navigate 组件与 Refine 中的实际应用
2026/9/10 7:53:00 网站建设 项目流程

React Router V6 重定向指南:深入理解 Navigate 组件与 Refine 中的实际应用

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

Navigate是 React Router V6 内置的声明式重定向组件,是useNavigateHook 的组件化等价物,通过渲染组件即可触发路由跳转。本文以该组件为主线,先讲解在 React 项目中安装、配置 React Router V6 并完成基础路由搭建,再深入剖析Navigate的三个 props 及登录成功后跳转的完整示例,随后结合 Refine 仓库中@refinedev/react-router包的源码,介绍NavigateToResourceCatchAllNavigate等基于Navigate扩展的实战组件,最后覆盖懒加载、代码分割、性能优化与路由测试等配套最佳实践。

React Router V6 起步

安装 React Router

在已有 React 项目中,根据你使用的包管理器,从 NPM 仓库安装 React Router V6:

# 使用 NPM npm install react-router-dom@6 # 使用 Yarn yarn add react-router-dom@6 # 使用 pnpm pnpm add react-router-dom@6

配置 BrowserRouter

在浏览器环境中使用 React Router 之前,需要用它的顶层组件包裹根组件。BrowserRouter是浏览器环境下官方推荐的方式,它利用 History API 让 UI 与 URL 保持同步:

import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter as Router } from "react-router-dom"; import App from "./App"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <Router> <App /> </Router> </React.StrictMode>, );

设置路由

React Router V6 中使用RoutesRoute两个组件声明路由。Routes会遍历所有Route,仅渲染与当前 URL 匹配的那一个:

import { Route, Routes } from "react-router-dom"; import { Home, Blog } from "./components"; function App() { return ( <div className="App"> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> </Routes> </div> ); }

如何使用 Navigate 组件

Navigate是 React Router V6 提供的若干内置组件之一,它内部封装了useNavigateHook。只要组件被渲染(render),当前 location 就会立即发生改变,因此它非常适合用于“条件渲染式”的重定向场景。

import { Navigate } from "react-router-dom";

Navigate最多接收三个 props,其中只有一个必填,其余两个可选:

<Navigate to="/dashboard" state={{ todos: [] }} replace={true} />
  • to—— 必填 prop,值为要跳转的目标路径。
  • replace—— 可选的布尔 prop。设为true时,将替换(replace)历史栈中当前的记录,而不是压入新记录。
  • state—— 可选 prop,用于把数据从渲染Navigate的组件传递给目标路由。

下面是一个完整示例:登录表单提交成功后更新 state,再条件渲染Navigate完成跳转,并通过stateprop 把用户对象传给目标页面:

const App = () => { const [loginDetails, setLoginDetails] = useState({ email: "", password: "" }); const [user, setUser] = useState(null); const changeHandler = (e) => { const { name, value } = e.target; setLoginDetails((loginDetails) => ({ ...loginDetails, [name]: value })); }; const submitHandler = async (e) => { e.preventDefault(); const user = await loginUser(loginDetails); setUser(user); }; return ( <div> <form onSubmit={submitHandler}> <label> Email: <input type="email" name="email" value={loginDetails.email} onChange={changeHandler} required /> </label> <label> Password: <input type="password" name="password" value={loginDetails.password} onChange={changeHandler} required /> </label> <button type="submit"> Login </button> </form> {user && <Navigate to="/dashboard" state={user} replace={true} />} </div> ); };

跳转完成后,匹配路由渲染的组件可以通过useLocationHook 读取stateprop 传递的数据:

const location = useLocation(); console.log(location.state); // 未传递任何数据时,location.state 的默认值为 null

需要注意:传给Navigate的 props,与useNavigateHook 返回的函数所要求的参数完全一致。在 React 中,函数式组件可以使用 Hooks,但 ES6 类组件不支持 Hooks,因此当你需要在类组件中实现重定向时,Navigate就是useNavigate的便捷替代方案。

在 Refine 中基于 Navigate 的实战扩展

在 Refine 项目中,路由体系建立在 React Router 之上,@refinedev/react-router包提供了将 Refine 与 React Router 深度集成的routerProvider以及若干基于Navigate扩展的组件。其实现位于 packages/react-router/src,下面逐一剖析。

NavigateToResource:跳转到资源页面

NavigateToResourceNavigate的扩展组件,常用于应用根路径(index route)自动重定向到某个资源的列表页。源码见 packages/react-router/src/navigate-to-resource.tsx:

export const NavigateToResource: React.FC<NavigateToResourceProps> = ({ resource: resourceProp, fallbackTo, meta, }) => { const getToPath = useGetToPath(); const { resource, resources } = useResourceParams({ resource: resourceProp, }); const toResource = resource || resources.find((r) => r.list); if (toResource) { const path = getToPath({ resource: toResource, action: "list", meta, }); if (path) { return <Navigate to={path} />; } return null; } if (fallbackTo) { console.warn(`No resource is found. navigation to ${fallbackTo}.`); return <Navigate to={fallbackTo} />; } console.warn( 'No resource and "fallbackTo" is found. No navigation will be made.', ); return null; };

从源码可以看到其三个可选 props 的行为:

  • resource(可选)——要跳转到的资源名称;如果不传,会重定向到resources数组中第一个定义了list路由的资源。
  • fallbackTo(可选)——当找不到任何资源时跳转的路径;如果未提供且没有找到资源,则不会发生任何跳转(并输出 console 警告)。
  • meta(可选)——当目标路由包含参数时使用的 meta 对象;当前 location 中的参数也会参与路由组合,但meta优先级更高。

对应测试用例见 packages/react-router/src/navigate-to-resource.test.tsx,其中通过 mockNavigate组件捕获to值,验证了“跳转到第一个带 list 的资源(/posts)”“跳转到指定资源(/categories)”“无资源时跳转 fallbackTo”等行为。

实际使用方式如下,把根路径/的路由元素设为NavigateToResource,用户访问应用根路径时即被重定向到/posts

import routerProvider, { NavigateToResource } from "@refinedev/react-router"; const App = () => { return ( <BrowserRouter> <Refine routerProvider={routerProvider} resources={[ { name: "posts", list: "/posts", }, ]} > <Routes> <Route path="/" element={<NavigateToResource resource="posts" />} /> <Route path="/posts" element={<PostList />} /> </Routes> </Refine> </BrowserRouter> ); };

CatchAllNavigate:带回调地址的重定向

CatchAllNavigate用于处理 404 页面与认证结合的场景:它会跳转到指定路径,并把当前 location 保存在to查询参数中,以便登录后跳回原页面。实现见 packages/react-router/src/catch-all-navigate.tsx:

export const CatchAllNavigate: React.FC<{ to: string }> = ({ to }) => { const { pathname, search } = useLocation(); const queryValue = `${pathname}${search}`; const query = queryValue.length > 1 ? `?to=${encodeURIComponent(queryValue)}` : ""; return <Navigate to={`${to}${query}`} />; };

其唯一必填 prop 是to(要跳转的路径)。例如catch-all路由中把它指向登录页:

<Route path="*" element={<CatchAllNavigate to="/login" />} />

当用户访问未定义的路由时,会被重定向到/login?to=%2Fposts,登录成功后即可依据to参数回到原目标页。

routerProvider 的 go 方法:Navigate 背后的导航能力

Navigate属于 React Router 自带能力,而 Refine 的routerProvider在此基础上进一步封装。在 packages/react-router/src/bindings.tsx 中,go方法内部调用了useNavigate,并支持totypepath/push/replace)、queryhash以及keepQuerykeepHash选项,最终通过navigate(fullPath, { replace: type === "replace" })完成跳转;parse方法则会解析当前 URL 中的资源、action、id 与查询参数。这也是理解 Refine 路由体系的关键入口,更多说明可参考 documentation/docs/routing/integrations/react-router/index.md 与 documentation/docs/routing/router-provider/index.md。如果你的业务需要更精细的导航控制,Refine 还提供了 useNavigation 与useGo等 Hook(前者为 legacy API,官方建议新代码优先使用useGo/useGetToPath)。

React Router V6 的进阶特性

懒加载路由(Lazy Loading)

懒加载能够按需加载组件,避免一次性加载全部页面代码,从而显著缩短首屏加载时间:

import React, { Suspense, lazy } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; // 懒加载组件 const Home = lazy(() => import("./components/Home")); const Blog = lazy(() => import("./components/Blog")); function App() { return ( <Router> <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> </Routes> </Suspense> </Router> ); } export default App;

上例中,HomeBlog仅在用户导航到对应路由时才被加载;Suspense起到占位符或 fallback UI(如 loading 指示器)的作用,直到组件加载完成。

代码分割(Code Splitting)

代码分割把应用代码拆分为多个小 bundle,按需加载,而不是一次性下载整个巨型文件。结合 React Router V6 的典型做法:

import React, { Suspense, lazy } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; // 懒加载组件 const Home = lazy(() => import("./components/Home")); const Blog = lazy(() => import("./components/Blog")); const Contact = lazy(() => import("./components/Contact")); function App() { return ( <Router> <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> <Route path="/contact" element={<Contact />} /> </Routes> </Suspense> </Router> ); } export default App;

HomeBlogContact被拆分为独立 bundle,仅在用户导航到对应路由时加载,从而把首屏需要下载的代码量降到最低。

性能优化

使用 Memoization 减少重渲染

React.memo可以缓存组件、避免不必要的重复渲染,仅在 props 变化时才重新渲染:

import React from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; const Home = React.memo(() => { console.log("Home component rendered"); return <h1>Home Page</h1>; }); const Blog = React.memo(() => { console.log("Blog component rendered"); return <h1>Blog Page</h1>; }); function App() { return ( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> </Routes> </Router> ); } export default App;

优化路由匹配

Routes组件一次只会匹配并渲染一个路由,这本身就是一种性能优化。在此基础上,把不常访问的大型路由改为懒加载可进一步提升性能:

import React, { Suspense, lazy } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; const Home = React.memo(() => <h1>Home Page</h1>); const Blog = React.memo(() => <h1>Blog Page</h1>); const About = lazy(() => import("./About")); function App() { return ( <Router> <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> </Router> ); } export default App;

此处<About>被懒加载,只有用户访问/about时才加载,有效降低了初始加载时间。将 memoization 与优化的路由匹配结合,可以显著改善应用性能与用户体验。

React Router 测试

路由单元测试

可以用 Jest 配合 React Testing Library 对路由组件做单元测试,验证每个组件在其路由下是否被正确渲染:

import React from "react"; import { render } from "@testing-library/react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import "@testing-library/jest-dom/extend-expect"; const Home = () => <h1>Home Page</h1>; const Blog = () => <h1>Blog Page</h1>; test("should render Home component for / route", () => { const { getByText } = render( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> </Routes> </Router>, ); expect(getByText("Home Page")).toBeInTheDocument(); }); test("renders Blog component for /blog route", () => { window.history.pushState({}, "Blog Page", "/blog"); const { getByText } = render( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> </Routes> </Router>, ); expect(getByText("Blog Page")).toBeInTheDocument(); });

第一个测试渲染默认路径并断言 Home 页面存在;第二个测试先用window.history.pushState把 URL 改到/blog,再断言 Blog 页面被渲染。

导航集成测试

集成测试会模拟用户从应用入口一路操作到最终状态的完整流程,包括页面间的导航行为:

import React from "react"; import { render, fireEvent } from "@testing-library/react"; import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom"; import "@testing-library/jest-dom/extend-expect"; const Home = () => ( <div> <h1>Home Page</h1> <Link to="/blog">Go to Blog</Link> </div> ); const Blog = () => ( <div> <h1>Blog Page</h1> <Link to="/">Go to Home</Link> </div> ); test("Goes from Home to Blog and back", async () => { const { getByText } = render( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> </Routes> </Router>, ); // 验证基础路由 expect(getByText("Home Page")).toBeInTheDocument(); // 跳转到 Blog fireEvent.click(getByText("Go to Blog")); expect(getByText("Blog Page")).toBeInTheDocument(); // 回到 Home fireEvent.click(getByText("Go to Home")); expect(getByText("Home Page")).toBeInTheDocument(); });

这里使用fireEvent模拟用户点击链接,并在每次导航后断言正确的组件被渲染。单元测试与集成测试相结合,可以确保 React Router 应用在真实用户交互下行为符合预期。

总结

Navigate是 React Router V6 内置的声明式重定向组件,是useNavigateHook 的组件等价物,内部即基于useNavigate实现;传给Navigate的 props 与传给useNavigate返回函数的参数完全一致。由于 React 的 ES6 类组件不支持 Hooks,Navigate是在类组件场景下替代useNavigate的便捷方案。

在实际的 Refine 项目中,@refinedev/react-router包在 React Router 之上进一步封装了routerProvider,并提供了NavigateToResourceCatchAllNavigate等基于Navigate的实用组件,用于资源列表页跳转与带回调地址的认证重定向;再配合懒加载、代码分割、memoization 优化与路由测试,即可构建出结构清晰、性能良好且易于维护的 React Router 应用。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询