深入探究React Router
在现代前端框架中有三座大山,UI渲染、状态管理、路由管理。UI渲染部分VUE和React平分秋色。React的状态管理库多如牛毛,但React的路由管理只有一个,react-router。可能是因为在路由库中只有它作为老大,近年来也无其他库能撼动,或也许它无论如何升级版本,都能够向下兼容,也可能是工作中还使用足够老的版本。学习的时候还是通过看其他人的博客,自打学完之后也很少关注。自己的项目属于能用就行,从来没有看过官方文档。所以今天就让我们玩一下吧。
三大主流版本
近年来,即使很多老项目逐步被淘汰,但目前主流的路由方案也对应了react-router分为三个阶段,分别是5+与6+,6.4,7+。
6版本相对于5版本更多的是体验上的优化,但还是声明式路由为主。去掉了Switch标签优化了体验,增加了Outlet优化了嵌套路由的体验,能够使用一些钩子函数。直到现在,在工作中有些入行早的开发还不知道什么是嵌套路由。
6.4是react router的大版本更新,其实它更像是7的前瞻版本。官方推荐函数式而不是声明式;懒加载还需要手动处理;增加了loader方法,能直接在这里调用接口,减少useEffect的使用等。同时也加入了部分SSR的能力。
7版本我不清楚多少人在用,因为它本质上是一个基于react的前端全栈框架而不是一个单纯的路由库。它是将remix和react-router的合并,你可以选择框架模式也可以选择库模式。这里我习惯使用库模式,因为选择全站框架我更会考虑Nextjs。抛开框架模式之外,其他的和6.4版本几乎一样,而起因为全都融入到react-router这个库里,引用依赖的过程会更好。
路由模式
说到路由就不得不提路由模式。在我早期的一篇文章中,我讲到了BrowserRouter和HashRouter。但那篇文章的目的是快速实战react,没有过于细致,而这篇文章是深入探究就把路由模式说的更透彻一些。这里我也请教了AI老师。
BrowserRouter:官方推荐的路由模式,主要利用浏览器的history栈进行存储路由信息。如果有对这方面不了解的小伙伴移步 历史模式。BrowserRouter漂亮 URL , 好 SEO,但需要服务器“兜底”。
HashRouter:官方不推荐的路由模式,如果你是个人开发demo可以使用,因为它不利于SEO。但是它任何静态文件服务器都能跑,零配置。
BrowserRouter
- 当用户点击
<Link>组件时,BrowserRouter使用history.pushState()更新 URL - 浏览器不会向服务器发送请求,只是更新历史记录
- 当用户刷新页面或直接访问 URL 时,请求会发送到服务器
- 需要服务器配置返回单页应用的主 HTML 文件
HashRouter
- 使用
window.location.hash来管理路由 - 监听
hashchange事件来响应 URL 变化 - 服务器只关心
#之前的部分,之后的内容由客户端处理 - 无论 URL 如何变化,服务器始终接收相同的请求
| 场景 | 推荐 |
|---|---|
| 有服务器控制权(Nginx、Node、Apache…) | Browser |
| 纯静态托管(GitHub Pages、OSS、旧式共享主机) | Hash |
| 需要 SEO / SSR / 分享链接 | Browser |
| 快速原型、内部工具、无 SSR 需求 | Hash 更省事 |
MemoryRouter:内存路由不使用浏览器的历史记录,而是在内存中管理自己的历史堆栈。它主要适用于 Storybook 等测试和组件开发工具,但也可用于在任何非浏览器环境中运行 React Router。(官网原话)如何理解呢,如果是一些不需要展示路由信息的地方,可以考虑这种。但是也就是意味它丧失了,url链接直接跳转的功能。
还有用在服务器中的StaticRouter,这里我们只讨论客户端的情况。
新增功能
lazy:react-router现在自带懒加载功能,再也不需要自己手写了。
loader:现在可以在这个地方发送请求,减少useEffect的使用。
重要hooks
react-router提供了几十个钩子,其中我认为这些钩子比较常用。
useLocation:能获取当前的location对象。如果需要路由传参,通过它获取。
useNavigate:用于路由跳转,可以携带参数。replace可以替换当前路由而不是在栈里添加一个。
useRoutes: 可以在外部定义router路由表。
useSearchParams : 如果通过url进行查询,可以使用这个钩子获取。
模拟keepalive
众所周知,不支持react不支持keepalive。想要实现切换tab页不清空数据的方法是给每个组件包裹一层div,通过display:none 和 display:block的切换做到keepalive。
//router.js
import { createBrowserRouter } from 'react-router-dom';
import App from './App';
export const router = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{ path: 'view1', element: null }, // 占位,实际由 App 渲染
{ path: 'view2', element: null },
],
},
]);
正常情况下,我们通过element进行渲染,但这里只是添加一个路由路径,并不依赖它渲染。
const View1 = ()=>{
const [count,setCount] = useState(null)
return <div>
<button>+1</button>
{count}
</div>
}
const View2 = ()=>{
return <div>
<input />
</div>
}
const Cache = ({path,children})=>{
const match = useMathce(path)
return <div style={match?{display:'block'}:{display:'none'}}>{children}</div>
}
const viewList = [
{element:<Cache path='view1'><View1 /></Cache>},
{element:<Cache path='view2'><View2 /></Cache>},
]
//App.jsx
import { useNavigate } from "react-router";
const App = ()=>{
const navigate = useNavigate();
return <div>
<ul>
<li onClick={()=>navigate('view1')}>view1</li>
<li onClick={()=>navigate('view1')}>view2</li>
</ul>
<div> {viewList.map(item=><div>{item.element}</div>)}
</div>
</div>
}
这样就能发现,状态没有被清除。如果需要更深层的嵌套,继续使用这种方式即可。这种方式很适合中后台项目。
总结
如果你是Spa单页面项目且node20以下版本的项目,直接推荐6.4,如果是node20+版本建议最新版。虽然7增加了框架模式,但打包后的体积几乎没有变化。
如果你有SSR的需求,或老项目增加SSR,直接使用最新版本。毕竟打包体积远小于Nextjs且同处于vite生态,可以进行一个尝试。