从手写 Hash 路由到 React Router:一个路由练习的关键实现
前端路由的目标是在单页应用中根据 URL 切换页面组件,而不按传统多页应用方式重新请求整份 HTML。本练习基于 React、Vite 和react-router-dom,集中实现了基础路由、动态参数、嵌套路由、404 与路由级懒加载。
本文依据本地代码静态分析整理,运行未验证。
1. 用 Router 接管页面切换
入口路由在App.jsx:
<Router> <Suspense fallback={<div>Loading...</div>}> <Navigation /> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/user/:id" element={<UserProfile />} /> <Route path="*" element={<NotFound />} /> </Routes> </Suspense> </Router>项目将HashRouter别名为Router。它使用#后的路径管理前端状态,例如#/about。Routes负责匹配,Route描述“路径对应哪个组件”。
导航栏用Link:
<Link to="/about">About</Link>Link让 React Router 处理跳转,适用于 SPA 路由切换。
2. 路由级懒加载:首屏不必下载全部页面
页面不是普通静态导入,而是:
const Home = lazy(() => import('./pages/Home')) const Products = lazy(() => import('./pages/Products'))lazy + import()实现代码分割:访问页面时才加载对应模块。由于异步加载需要等待时间,外层必须由Suspense包裹并显示加载占位。
这适合管理后台、图表、编辑器或访问频率低的页面;不应把每个很小且高频的组件都拆分成独立包。
3. 动态参数:同一页面处理不同资源
用户页规则:
<Route path="/user/:id" element={<UserProfile />} />访问/user/123后:
const { id } = useParams()可得到字符串'123'。产品详情使用相同模式读取:productId。
真实业务中,参数通常用于请求详情接口,并应处理参数格式校验、加载状态和接口异常。
4. 嵌套路由:共享布局与子页面出口
产品模块:
<Route path="/products" element={<Products />}> <Route path=":productId" element={<ProductDetail />} /> <Route path="new" element={<ProductNew />} /> </Route>父组件提供:
<Outlet />当访问/products/123时,Products先渲染共享标题,ProductDetail再渲染到Outlet位置。该模式适合共享侧栏、Tab、权限逻辑的业务模块。
5. 404、重定向与编程式跳转
未知地址由通配符兜底:
<Route path="*" element={<NotFound />} />404 页通过useNavigate在逻辑中跳转:
const navigate = useNavigate() navigate('/')而Navigate用于声明式重定向:
<Route path="/old-path" element={<Navigate to="/new-path" />} />两个代码审查点
/new-path未见对应路由配置,按当前代码推断会进入 404;需要补充目标路由或修改跳转目标。- 404 页创建了 3 秒定时器但没有清理。建议在
useEffect中返回clearTimeout(timer),避免组件提前卸载后仍触发跳转。
6. 小结
这个练习覆盖了 React Router 的主干能力:
HashRouter提供路由上下文;Routes、Route描述路径与页面;Link用于导航;lazy + Suspense用于按需加载;useParams读取动态参数;Outlet承载嵌套路由;Navigate、useNavigate分别处理声明式与编程式跳转;path="*"负责 404 兜底。
面试中可以总结为:我完成了一个 React SPA 路由练习,并在基础路由之上补充了页面级代码分割、动态参数和嵌套路由;同时关注到重定向目标必须存在、异步副作用必须清理等可靠性问题。