☰
为什么OpenRepo不用React Router?16行useHashRoute实现轻量哈希路由的完整原理
2026/10/25 12:22:51 网站建设 项目流程

【免费下载链接】cv

项目地址:https://gitcode.com/gh_mirrors/cv115/cv
点击查看免费下载

OpenRepo 是一个集合浏览器工具、互动小游戏与精选开源库目录的开源网站,它没有引入 React Router,而是用一个仅 16 行的自定义 HookuseHashRoute实现了全站轻量哈希路由(hash routing)。本文带你完整读懂这套方案:为什么静态托管场景下哈希路由更省心、16 行源码每一行在做什么、以及子路由和查询参数是如何工作起来的。

🧭 先认识 OpenRepo:一个"纯静态"的开源工具站

OpenRepo 把 CV Creator 简历生成、Dev Tools 开发者工具箱、Typing Survival 打字游戏、Refresh Man 街机游戏、Landing Pages 模板合集和精选开源仓库目录,全部装进一个单页应用里。它有两个关键特征:

  • 部署在纯静态托管上:整个站点构建后就是一个静态目录,vite.config.js 中设置了base: './',让资源在任意子路径下都能加载;
  • 所有"页面"都是 React 组件:导航不靠刷新页面,而是靠 URL 里的#哈希片段切换。

正是这两个特征,决定了它不需要 React Router 这样的重型路由库。

📦 为什么不用 React Router?3 个现实原因

原因一:静态托管下的深度链接难题。如果使用历史路由(/dev-tools这种形式),用户直接打开或刷新站点地址/dev-tools时,服务器会去找一个真实存在的dev-tools文件,找不到就返回 404,除非配置服务端 rewrite 规则。而哈希路由的 URL 是站点地址/#/dev-tools,#之后的内容根本不会发给服务器,服务器永远只返回同一个index.html——零配置就能支持任意页面刷新和分享。

原因二:路由需求非常简单。OpenRepo 的路由结构是"一层平铺"的:一个哈希字符串对应一个页面组件,没有嵌套路由、没有路径参数、没有导航守卫。为这种需求引入完整路由框架,就像用起重机拧螺丝。

原因三:零新增依赖。查看 package.json 可以发现,项目依赖只有react、react-dom、framer-motion等几个包,没有 react-router。全站路由能力来自 useHashRoute.js 这一个 16 行的文件——没有网络请求解析器、没有匹配算法、没有 Provider 包裹,加载成本几乎为零。

🔍 16 行 useHashRoute 逐段拆解

下面是 useHashRoute.js 的完整源码,这也是整篇文章的主角:

export function useHashRoute() { const [route, setRoute] = useState(() => window.location.hash) useEffect(() => { const onHashChange = () => { setRoute(window.location.hash) window.scrollTo(0, 0) } window.addEventListener('hashchange', onHashChange) return () => window.removeEventListener('hashchange', onHashChange) }, []) return route }

拆开看只有 4 件事:

代码作用
useState(() => window.location.hash)惰性初始化:首次渲染时直接读取当前 URL 的哈希,作为初始路由
addEventListener('hashchange', ...)监听浏览器原生hashchange事件——用户点击#/开头的链接时浏览器会自动触发它
setRoute(window.location.hash)把最新哈希写入 React 状态,触发组件重渲染,路由"切换"就完成了
window.scrollTo(0, 0)每次换页把滚动位置重置到顶部,模拟"新页面"体验
removeEventListener清理函数组件卸载时移除监听,避免 React 18+ 严格模式下重复绑定

值得体会的是:整套路由没有一行"匹配"逻辑。Hook 只负责回答一个问题——"现在的哈希是什么?"至于这个哈希该显示哪个页面,交给下一层处理。这就是"关注点分离":监听事件归 Hook,路由表归路由表。

⚡️ 从 #/dev-tools/json 到页面组件:完整流程

路由的"最后一公里"发生在 App.jsx 和 routes.jsx 中:

  1. 用户点击一个#/开头的链接,浏览器改变 URL 哈希并触发hashchange;
  2. useHashRoute把新哈希存入状态,App组件重渲染,拿到最新route;
  3. App调用renderRoute(route),在静态路由表中查表。

registry.js 与 routes.jsx 的核心结构如下:

export const routes = { '#/cv': CvPage, '#/portfolio': PortfolioPage, '#/repos': RepositoriesPage, '#/repos?contribute=1': RepositoriesPage, ...productRoutes, } export function renderRoute(route) { if (experimentRoutes.has(route)) return null const Page = routes[route] ?? routes[route.split('/').slice(0, 2).join('/')] return Page ? <Page /> : null }

这里有两个巧妙的设计:

  • 路由表是"自动生长"的。productRoutes不是手写的,而是由 products.js 里的产品目录自动生成——每个产品在各自的product.js中声明route: '#/dev-tools'和Page组件,目录校验通过后自动进入路由表。新增一个工具只需要在目录里登记一行,不用碰任何路由代码(见 CONTRIBUTING.md)。
  • 两级查表 + 前缀兜底。第一行先精确匹配;匹配不上时,把哈希按/切开取前两段拼回去再查一次。所以#/dev-tools/json会兜底到#/dev-tools对应的页面,而子路径json留给页面自己去解析。这就是"顶层只做粗匹配,页面自解析细路由"的分层设计。
  • 未命中就回首页。如果两级都查不到(比如#/neo这类主题实验路由),renderRoute返回null,App.jsx 会兜底渲染HomePage,并让首页换上对应视觉主题。

🧩 子路由与参数:项目里的 3 个真实用法

① Dev Tools 的标签页(路径子路由)。访问#/dev-tools/regex时,顶层路由表兜底命中 Dev Tools 页面,然后 DevToolsPage.jsx 在页面内部再次调用useHashRoute,用一个正则从哈希中提取工具 id:

const match = route.match(/^#\/dev-tools\/([a-z0-9-]+)/)

命中哪个工具就渲染哪个组件。一个 Hook 用在两个层级,代码照样只有几行。

② Landing Pages 的模板详情页(路径段解析)。#/landing-pages/saas这类 URL 由 LandingPagesPage.jsx 解析,工具函数 routeParams.js 只有一行核心逻辑:

return hash.replace(/^#\/?/, '').split('/').filter(Boolean)

去掉#/前缀、按斜杠切段、过滤空串,然后取第二段作为模板 slug。

③#/repos?contribute=1(哈希里的"查询参数")。这个完整字符串被直接当作路由表的键(routes.jsx),和普通路由指向同一个页面;页面挂载时检测到这个哈希,就自动滚动并聚焦到贡献表单(RepositoriesPage.jsx)。没有路由库也能实现"链接直达表单"这种带参数的深链接。

⚖️ 哈希路由 vs 历史路由:一张表看懂

对比维度哈希路由#/dev-tools历史路由/dev-tools
URL 美观度带#,略长干净简洁
静态托管刷新/直达✅ 无需任何服务器配置❌ 需 rewrite 规则,否则 404
#之后是否发给服务器不发送路径会进入请求
实现成本16 行 Hook 即可通常引入完整路由库
适用场景静态站、中小规模 SPA大型应用、对 URL 形态要求严格的站点

OpenRepo 的取舍很清晰:用#换来零配置部署和 16 行代码,这对一个部署在静态托管上的工具站来说是最优解。

🎯 什么时候该自己写?什么时候交给 React Router?

  • 适合手写哈希路由:页面数量不多、路由是平铺的、部署在纯静态环境(如静态托管子路径)、想严格控制包体积——OpenRepo 就是典型样本。
  • 该用 React Router 的时候:多层嵌套路由、动态路径参数(/user/:id)、导航守卫与懒加载编排、团队协作需要统一路由心智模型。此时路由库带来的可维护性远大于体积成本。

一句话:先问"我的路由结构有多复杂、部署环境有什么限制",再选方案,而不是默认所有 React 项目都要加路由库。

🚀 本地运行 OpenRepo 与源码地图

想亲手验证这套路由,本地跑起来只需要三步:

git clone https://gitcode.com/gh_mirrors/cv115/cv cd cv && npm install && npm run dev

然后打开本地地址,试着在#/后敲入#/dev-tools/qr、#/landing-pages/saas、#/repos?contribute=1,再按刷新键——你会发现页面从不"迷路"。

路由相关源码地图(全部为仓库内相对路径):

  • 路由 Hook 本体:src/app/useHashRoute.js
  • 应用入口与兜底逻辑:src/app/App.jsx
  • 静态路由表与前缀兜底:src/app/routes.jsx
  • 哈希切段工具:src/app/routeParams.js
  • 目录自动生成路由表:src/catalog/registry.js
  • 子路由解析示例:src/products/dev-tools/DevToolsPage.jsx
  • 路由约定说明:README.md 中的 Routing 一节

✅ 小结

  • OpenRepo 放弃 React Router 的核心原因:静态托管零配置、路由结构扁平、零新增依赖;
  • 16 行useHashRoute只做一件事:监听hashchange、把哈希存进状态,剩下的交给路由表查表;
  • 两级查表 + 前缀兜底 + 目录自动生成路由,让"新增页面"变成"登记一行元数据";
  • 子路由(Dev Tools 标签)、路径段(Landing Pages)、哈希参数(?contribute=1)三种玩法,证明轻量哈希路由足以覆盖中小项目的大部分需求。

【免费下载链接】cv

项目地址:https://gitcode.com/gh_mirrors/cv115/cv
点击查看免费下载

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

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

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

立即咨询