React Router v6 实战笔记整理

44 阅读5分钟

最近在啃 React 的全家桶,学到 react-router-dom (v6) 这块时,感觉概念稍微有点多。以前只知道写静态页面(切图),现在接触单页应用(SPA),发现前端竟然也能自己控制路由跳转,不用每次都求着后端改 URL,感觉打开了新世界的大门!

为了防止自己学了忘,我把这几天的学习心得、代码实现和踩过的坑整理了一下,希望能帮到跟我一样的人。

一、 为什么要用前端路由?

以前写传统网页(多页应用)时,每次点个链接,浏览器都要去找服务器重新请求这一页的 HTML,页面会“白”一下,体验不太连贯。

现在的单页应用(SPA),页面加载完后,路由切换全归前端管:

  1. URL 变了
  2. 页面不刷新
  3. JS 监听到变化,把对应的组件渲染到页面上

这就是 react-router-dom 帮我们干的事。

二、 两种路由模式:选谁?

index.js 或者 App.js 里包裹路由时,我有两个选择,特意查了一下区别:

  • HashRouter (#/)

    • 样子:URL 后面带着 #,比如 localhost:3000/#/home
    • 评价:虽然丑了点(像个锚点),但是它性格“温柔” ,兼容性贼好,老浏览器也能跑,部署上线时不需要后端配合,一般不会 404。
  • BrowserRouter (/)

    • 样子localhost:3000/home,长得跟后端路由一样,正规、漂亮。
    • 评价:它是基于 HTML5 History API 的。现在主流都用这个,但如果上线,需要后端配置一下(把所有请求都指向 index.html),否则刷新页面可能会 404。

我的选择:作为“颜控”,我用了 BrowserRouter,并且为了代码可读性,给它起了个别名 Router

JavaScript

// App.js
import { BrowserRouter as Router } from 'react-router-dom'; 
import RouterConfig from './router';
// ...其他引入

export default function App() {
  return (
    <Router>
      <Navigation /> {/* 导航栏放在这里,切换路由时它不动 */}
      <RouterConfig /> {/* 这里是变的区域 */}
    </Router>
  )
}

三、 路由懒加载

刚开始学的时候,我把所有组件都 import 进来了。后来发现,如果页面很多,第一次打开网页会加载巨慢。

于是我学到了 懒加载 (Lazy Load) :不到那个页面,就不加载那个 JS 文件。

这里要配合 React 的 Suspense 组件,不然网络慢的时候页面空着会报错。我还特意手写了个 CSS 动画的 LoadingFallback 组件(代码在最后),让等待过程稍微优雅一点。

JavaScript

// router/index.js
import { lazy, Suspense } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import LoadingFallback from '../components/LoadingFallback';

// 这里的 import 要放在 lazy 里
const Home = lazy(() => import('../pages/Home'));
const About = lazy(() => import('../pages/About'));
const UserProfile = lazy(() => import('../pages/UserProfile'));
// ...

export default function RouterConfig() {
  return (
    <Suspense fallback={<LoadingFallback/>}>
      <Routes>
         {/* 具体的路由规则写在下面 */}
      </Routes>
    </Suspense>
  )
}

四、 几种常见的路由写法

Routes 里面配置规则,感觉就像是在搭积木。整理了几个我常用的场景:

1. 动态路由 (怎么传参?)

比如用户详情页,每个人 ID 不一样,不能写死。

  • 配置:用 :id 占位。
  • 获取:用 useParams 钩子。

JavaScript

{/* 路由配置 */}
<Route path="/user/:id" element={<UserProfile />} />

JavaScript

// UserProfile.js 组件内部
import { useParams } from 'react-router-dom';

export default function UserProfile() {
    const { id } = useParams(); // 拿到路由里的参数
    return <>用户ID是: {id}</>;
}

2. 嵌套路由

这一块我理解了好久。比如“产品页”,它下面有“产品详情”、“新增产品”。它们应该共用一个父级的布局(比如都有个产品列表的侧边栏)。

关键点:父组件里要写一个 <Outlet />,子路由的内容就会显示在 <Outlet /> 的位置。

JavaScript

{/* 父路由 */}
<Route path="/products" element={<Product/>}>
    {/* 子路由1:/products/new */}
    <Route path="new" element={<NewProduct />}/>
    {/* 子路由2:/products/123 */}
    <Route path=":productId" element={<ProductDetail />}/>
</Route>

3. 重定向与 404

以前好像是用 Redirect,v6 变成了 <Navigate />

JavaScript

{/* 访问旧路径,自动跳到新路径 */}
<Route path="/old-path" element={<Navigate replace to="/new-path"/>}/>

{/* 通配符 *,匹配不到的都去 404 页面 */}
<Route path="*" element={<NotFound />}/>

五、 路由守卫:做一个鉴权组件

很多页面(比如支付页)是不能随便进的,必须登录。我写了一个高阶组件 ProtectRoute 来包裹这些页面。

逻辑很简单:判断 localStorage 里有没有登录标记。如果没有,就强制跳回登录页。

JavaScript

// components/ProtectRoute.js
import { Navigate } from 'react-router-dom';

export default function ProtectRoute({ children }) {
  const isLoggedIn = localStorage.getItem('isLogin') === 'true';
  
  if (!isLoggedIn) {
    // 没登录?踢回登录页
    return <Navigate to="/login"/>;
  }
  
  // 登录了?正常显示
  return <>{children}</>;
}

使用的时候就这样包一下:

JavaScript

<Route path="/pay" element={
  <ProtectRoute>
    <Pay />
  </ProtectRoute>
}/>

六、 进阶:手写导航高亮

虽然官方有 NavLink,但我为了练习 Hooks,试着自己写了一个判断高亮的逻辑。用到了 useResolvedPathuseMatch

这样我可以精确控制:是“完全匹配”才高亮,还是只要“包含路径”就高亮。

JavaScript

// Navigation.js
const isActive = (to) => {
    const resolvedPath = useResolvedPath(to);
    // end: true 代表要精确匹配
    const match = useMatch({ path: resolvedPath.pathname, end: true });
    return match ? 'active' : ''; // 返回 class 名
}

效果

image.png

七、 有意思的小功能

1. 404 页面自动跳转

NotFound 页面,我加了个定时器,6秒后自动把用户送回首页。这里用到了 useNavigate

JavaScript

let navigate = useNavigate();
useEffect(() => {
    // 6秒后回家
    const timer = setTimeout(() => {
        navigate('/');
    }, 6000);
    return () => clearTimeout(timer); // 记得清除定时器,防止内存泄漏
}, [navigate]);

2. 纯 CSS 的 Loading 动画

为了配合懒加载,我整了个 CSS Module 的动画,看着还挺像模像样的(代码就不全贴了,主要是用了 @keyframes 做旋转)。

image.png

学习总结

折腾下来,对 React Router 算是更加了解了。

  • 单页应用体验确实好,但也需要处理加载性能问题(懒加载)。
  • v6 版本的变化还是挺大的,比如 Switch 变成了 RoutesRedirect 变成了 Maps,感觉写法上更直观了。
  • Outlet 真的是嵌套路由的神器。