React Router V6 手摸手随便指南指北React Router V6 手摸手随便指南指北 目录 简介 为什么
React Router V6 手摸手随便指南指北
目录
- 简介
- 为什么升级到React Router V6
- React Router V6的主要特性
- 3.1 基本使用
- 3.2 动态路由
- 3.3 路由嵌套
- 3.4 重定向与导航
- 3.5 使用useNavigate进行编程式导航
- 高级特性
- React Router V6原理解析
- 从V5迁移到V6
- 最佳实践
- 常见问题与解答
- 结论
简介(≈废话)
React Router是React生态系统中最流行的路由库之一。随着V6版本的发布,它带来了许多新特性和改进,使得路由管理变得更加简单和强大。本教程将全面介绍React Router V6的使用方法、新特性以及背后的原理。
ps:其实本来是不想写的,由于单位的小伙子执着于新特性的探索,写一篇能够讲清楚的文章。个人认为当你学习一样新东西的时候你只是动手写了这还不够,如果你能够写去尝试然后将你的这个过程文档化,我相信你会有更多的收获,
为什么升级到React Router V6
升级到React Router V6有以下几个主要原因 (我个人建议是不升,新项目可以尝试):
- 更简单的API:V6简化了许多API,使得代码更加简洁。
- 更强大的路由嵌套:V6提供了更直观的路由嵌套方式。
- 自动化的路由配置:不再需要手动设置Switch和exact属性。
- 更好的TypeScript支持:增强了对TypeScript的类型推断。
- 一致的行为:统一了路由和链接的行为,避免了V5中的一些不一致性。
- 性能优化:V6在内部进行了多项优化,提高了路由的性能。
React Router V6的主要特性
1. 基本使用
首先,让我们看一个基本的路由配置:
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
function App() {
return (
<Router>
{/* 使用 BrowserRouter 来创建路由器 */}
<Routes>
{/* 定义路由规则 */}
<Route path="/" element={<Home />} /> {/* 匹配根路径('/') */}
<Route path="/about" element={<About />} /> {/* 匹配路径'/about' */}
<Route path="/contact" element={<Contact />} /> {/* 匹配路径'/contact' */}
<Route path="/contact" element={<Error404 />} /> {/* 匹配路径'/error404' */}
</Routes>
</Router>
);
}
function Home() {
return <h2>Home Page</h2>;
}
function About() {
return <h2>About Page</h2>;
}
function Contact() {
return <h2>Contact Page</h2>;
}
export default App;
2. 动态路由
V6中动态路由的使用更加直观:
<Route path="/user/:id" element={<User />} />
// 在User组件中
import { useParams } from 'react-router-dom';
function User() {
let { id } = useParams();
return <h2>User ID: {id}</h2>;
}
3. 路由嵌套
V6强化了嵌套路由的能力:
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
</Route>
</Routes>
</Router>
);
}
function Layout() {
return (
<div>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</nav>
<Outlet />
</div>
);
}
4. 重定向与导航
使用Navigate
组件处理重定向:
import { Navigate } from 'react-router-dom';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/old-path" element={<Navigate to="/new-path" />} />
<Route path="/new-path" element={<NewPage />} />
</Routes>
</Router>
);
}
5. 使用useNavigate进行编程式导航
import { useNavigate } from 'react-router-dom';
function Home() {
// 定义一个导航函数,用于跳转页面
let navigate = useNavigate();
// 点击事件处理函数
function handleClick() {
// 使用导航函数跳转到 "/about" 路径
navigate('/about');
}
return (
<button onClick={handleClick}>跳转到关于页面</button>
);
}
6. 使用 useSearchParams 处理查询参数
React Router V6 引入了 useSearchParams
钩子,它提供了一种便捷的方式来读取和修改 URL 的查询字符串。这个钩子返回一个包含两个元素的数组:当前位置的搜索参数和一个用于更新它们的函数。
基本用法如下:
import { useSearchParams } from 'react-router-dom';
function SearchComponent() {
// 解构 useSearchParams hook,得到搜索参数对象和更新函数
const [searchParams, setSearchParams] = useSearchParams();
function handleSubmit(event) {
event.preventDefault(); // 阻止表单默认提交行为
// 获取表单输入框的值
const query = event.target.query.value;
// 更新搜索参数,设置 q 参数为输入值
setSearchParams({ q: query });
}
return (
<div>
<form onSubmit={handleSubmit}>
<input name="query" placeholder="Search..." />
<button type="submit">Search</button>
</form>
<p>
The current query is: {searchParams.get('q') || 'none'} {/* 显示当前查询参数,如果没有则显示 'none' */}
</p>
</div>
);
}
export default SearchComponent; // 可选:导出 SearchComponent 组件
在这个例子中:
- 我们使用
useSearchParams()
获取当前的搜索参数和更新它们的函数。 searchParams.get('q')
用于获取名为 'q' 的查询参数的值。setSearchParams({ q: query })
用于更新查询参数。这会导致 URL 更新,但不会引起页面刷新。
高级用法
useSearchParams
还支持更复杂的操作:
-
多个参数:
setSearchParams({ q: query, page: '1' });
-
删除参数:
searchParams.delete('page'); setSearchParams(searchParams);
-
检查参数是否存在:
if (searchParams.has('q')) { // 执行搜索 }
-
获取所有参数:
for (let [key, value] of searchParams.entries()) { console.log(key, value); }
注意事项
-
useSearchParams
返回的searchParams
对象是不可变的。要修改查询参数,必须使用setSearchParams
。 -
当使用
setSearchParams
时,它会替换所有现有的查询参数。如果你只想更新部分参数,需要先获取当前参数,然后合并新的参数:setSearchParams(prevParams => { prevParams.set('page', '2'); return prevParams; });
-
useSearchParams
和 React 的状态管理是独立的。更新查询参数不会直接触发组件重新渲染,除非你的组件正在读取这些参数。
与服务端渲染的兼容性
如果你的应用使用服务端渲染,你可能需要使用 useSearchParams
的一个变体:
import { useSearchParams } from 'react-router-dom';
function SearchComponent() {
let [searchParams, setSearchParams] = useSearchParams();
// 在服务端渲染时,提供一个默认值
searchParams = searchParams || new URLSearchParams();
// 其余代码保持不变
}
这确保了即使在服务端(没有实际的 URL)也能正常工作。
通过使用 useSearchParams
,你可以轻松地管理 URL 查询参数,实现更复杂的路由逻辑和状态管理,特别适用于需要保存和共享搜索、过滤或分页状态的场景。这进一步增强了 React Router V6 的功能,使得构建功能丰富的单页应用变得更加简单。
高级特性
路由懒加载
React Router V6结合React的Suspense
和lazy
可以轻松实现路由懒加载:
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
}
自定义钩子
往后站一站我要开始装X了
自定义钩子是 React 中复用逻辑的强大方式。在 React Router 中,我们可以创建自定义钩子来封装和复用常用的路由逻辑,使得代码更加清晰和易于维护。以下是一个扩展的 useRouter
自定义钩子示例:
import {
useLocation,
useNavigate,
useParams,
useSearchParams
} from 'react-router-dom';
function useRouter() {
// 获取当前位置对象
const location = useLocation();
// 获取用于编程式导航的函数
const navigate = useNavigate();
// 获取 URL 参数
const params = useParams();
// 获取查询参数
const [searchParams, setSearchParams] = useSearchParams();
return {
// 当前路径
pathname: location.pathname,
// 查询字符串
search: location.search,
// 完整的 URL 路径
fullPath: location.pathname + location.search,
// 哈希值
hash: location.hash,
// 导航函数
navigate,
// 路由参数
params,
// 获取指定查询参数
getQuery: (key) => searchParams.get(key),
// 设置查询参数
setQuery: (key, value) => {
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.set(key, value);
setSearchParams(newSearchParams);
},
// 移除查询参数
removeQuery: (key) => {
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.delete(key);
setSearchParams(newSearchParams);
}
};
}
这个扩展的 useRouter
钩子提供了更多功能:
pathname
:获取当前路径。search
:获取查询字符串。fullPath
:获取完整的 URL 路径(包括查询字符串)。hash
:获取 URL 的哈希值。navigate
:用于编程式导航的函数。params
:获取路由参数。getQuery
:获取指定的查询参数值。setQuery
:设置查询参数。removeQuery
:移除指定的查询参数。
使用这个自定义钩子的例子:
function SomeComponent() {
const router = useRouter();
useEffect(() => {
// 获取当前路径
console.log('Current path:', router.pathname);
// 获取路由参数
console.log('Route params:', router.params);
// 获取查询参数
console.log('Query param "id":', router.getQuery('id'));
}, []);
const handleNavigate = () => {
// 使用导航函数
router.navigate('/new-path');
};
const handleSetQuery = () => {
// 设置查询参数
router.setQuery('filter', 'active');
};
return (
<div>
<button onClick={handleNavigate}>Navigate</button>
<button onClick={handleSetQuery}>Set Query</button>
</div>
);
}
通过使用这个自定义钩子,我们可以在组件中更方便地访问和操作路由相关的信息和功能。这不仅使代码更加简洁,还提高了可重用性和可维护性。在大型应用中,这种封装可以显著减少重复代码,并为团队提供一致的路由操作接口。
路由守卫
实现路由守卫可以使用自定义组件:
function PrivateRoute({ children }) {
const auth = useAuth();
const navigate = useNavigate();
if (!auth.user) {
// 重定向到登录页
return <Navigate to="/login" />;
}
return children;
}
// 使用
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
React Router V6原理
-
路由匹配算法:V6使用递归匹配算法,根据路由配置自动匹配最符合的路径。这个算法的工作方式如下: a. 从最顶层的路由开始,将当前URL路径与每个路由的路径模式进行比较。
b. 如果找到匹配,则继续检查该路由的子路由。
c. 这个过程递归进行,直到找到最深层的匹配路由或遍历完所有可能的路由。 d. 算法会选择"最具体"的匹配,即路径最长的匹配。 例如,对于URL "/users/123/profile",路由 "/users/:id/profile" 会比 "/users/:id" 更优先匹配。
-
基于上下文的数据传递:使用React Context API来在组件树中传递路由信息。这允许任何深度的子组件都能访问到当前的路由状态,而不需要通过props层层传递。
-
组件化的设计:每个路由功能都被封装成独立的组件或钩子,方便组合和复用。例如,
<Route>
、<Link>
、useParams()
等都是独立的单元,可以灵活组合使用。 -
路由配置的扁平化:V6简化了路由配置,不再需要严格的嵌套结构。这使得路由配置更加灵活,也更容易理解和维护。
从V5迁移到V6
从V5迁移到V6的主要步骤包括:
- 替换
Switch
为Routes
。 - 移除
exact
属性,V6默认精确匹配。 - 将
component
属性改为element
。 - 使用
useNavigate
替代useHistory
。 - 调整嵌套路由的结构,使用
Outlet
。
示例:
替换Switch
为Routes
:
// V5
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
// V6
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
// V5
<Route exact path="/users" component={Users} />
// V6
<Route path="/users" element={<Users />} />
将component
属性改为element
:
// V5
<Route path="/contact" component={Contact} />
// V6
<Route path="/contact" element={<Contact />} />
使用useNavigate
替代useHistory
:
// V5
import { useHistory } from 'react-router-dom';
const history = useHistory();
history.push('/home');
// V6
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
navigate('/home');
调整嵌套路由的结构,使用Outlet
:
<Outlet>
是一个特殊的占位符组件,用于在嵌套路由中渲染匹配到的子路由组件。它就像一个“空位”,等待着被子路由的内容填充。
主要作用:
- 实现嵌套路由: 将子路由组件渲染到指定位置。
- 创建共享布局: 在不同页面中共享相同的布局。
- 动态渲染内容: 根据 URL 动态渲染不同组件。
工作原理:
React Router 根据 URL 匹配路由,并将匹配到的子路由组件渲染到 <Outlet>
的位置。
总结:
<Outlet>
是构建嵌套路由的核心组件,它让你的应用结构更清晰,维护更方便。
// V5
<Route path="/users">
<Route path="/" component={UserList} />
<Route path="/:id" component={UserDetail} />
</Route>
// V6
<Route path="/users" element={<UserLayout />}>
<Route index element={<UserList />} />
<Route path=":id" element={<UserDetail />} />
</Route>
// UserLayout.js
import { Outlet } from 'react-router-dom';
function UserLayout() {
return (
<div>
<h1>Users</h1>
<Outlet />
</div>
);
}
-
解释:
Outlet
组件是一个占位符,用于渲染匹配到的子路由组件。- 在
UserLayout
组件中,<Outlet />
会被替换成对应的子路由组件(UserList
或UserDetail
)。
两者对比
- V5 使用
component
属性指定组件,而 V6 使用element
属性。 - V5 没有明确的布局组件概念,V6 引入了
Outlet
组件来处理布局。 - V6 的写法更简洁,语义更清晰。
最佳实践
- 使用声明式路由配置,避免硬编码路径。
- 充分利用路由嵌套来组织复杂的应用结构。
- 使用懒加载优化大型应用的性能。
- 创建自定义钩子来封装常用的路由逻辑。
- 使用TypeScript来增强类型安全。
常见问题与解答
-
Q: 如何处理404页面? A: 使用
*
路径匹配所有未定义的路由:<Route path="*" element={<NotFound />} />
-
Q: 如何实现路由过渡动画? A: 可以结合React Transition Group库实现:
import { CSSTransition, TransitionGroup } from 'react-transition-group'; function App() { const location = useLocation(); return ( <TransitionGroup> <CSSTransition key={location.pathname} classNames="fade" timeout={300}> <Routes location={location}> {/* 路由配置 */} </Routes> </CSSTransition> </TransitionGroup> ); }
-
Q: 如何在路由变化时滚动到页面顶部? A: 可以创建一个自定义组件:
function ScrollToTop() { const { pathname } = useLocation(); useEffect(() => { window.scrollTo(0, 0); }, [pathname]); return null; } // 在Router内使用 <Router> <ScrollToTop /> {/* 其他组件 */} </Router>
-
Q: 如何处理认证路由? A: 可以创建一个高阶组件来处理认证逻辑:
function RequireAuth({ children }) { let auth = useAuth(); let location = useLocation(); if (!auth.user) { return <Navigate to="/login" state={{ from: location }} replace />; } return children; } // 使用 <Route path="/protected" element={ <RequireAuth> <ProtectedComponent /> </RequireAuth> } />
-
Q: 如何实现动态路由? A: 可以使用路由参数和
useParams
钩子:<Route path="/users/:userId" element={<UserProfile />} /> function UserProfile() { let { userId } = useParams(); // 使用 userId 获取用户数据 return <div>User Profile for user {userId}</div>; }
-
Q: 如何在路由组件外访问路由信息? A: 可以使用
useNavigate
、useLocation
等钩子,但需要确保组件在 Router 上下文中:import { useNavigate } from 'react-router-dom'; function SomeComponent() { let navigate = useNavigate(); function handleClick() { navigate('/some-path'); } return <button onClick={handleClick}>Go to Some Path</button>; }
结论
React Router V6通过简化API、增强路由嵌套能力、改进TypeScript支持等特性,为开发者提供了更好的路由管理解决方案。它不仅使得代码更加简洁,也提高了应用的性能和可维护性。无论是新项目还是正在考虑升级的既有项目,React Router V6都是一个值得采用的选择。
看到这里,相信你应该已经掌握了React Router V6的基本使用方法、高级特性以及一些最佳实践。随着你在实际项目中的应用,相信你会发现更多React Router V6带来的便利之处。(非必要不要去动老项目)
转载自:https://juejin.cn/post/7403910440608907327