Skip to content

在react中使用react-router

1. 为什么我们需要 React Router?

传统的网页应用,每次点击链接都会向服务器发送请求,然后服务器返回一个全新的 HTML 页面,导致页面白屏和加载延迟。而单页应用(SPA)的核心理念是:只加载一次 HTML 页面,然后通过 JavaScript 动态地更新页面内容。

  • 问题: 既然只有一个 HTML 页面,那用户怎么能在“不同页面”之间切换呢?比如从“首页”到“关于我们”页面,URL 怎么变化?

  • 解决方案:

    这就是

    客户端路由

    的作用。React Router 就是最流行的客户端路由库之一,它帮助我们:

    • 将 URL 映射到 React 组件: 当 URL 变化时,它会渲染对应的组件。
    • 实现无刷新导航: 页面内容在客户端动态更新,避免了白屏。
    • 管理历史记录: 支持浏览器的前进、后退按钮。

2. React Router 的核心概念和组件

React Router v6+ 引入了一些新的概念和更简洁的 API。

  • BrowserRouter

    • 作用: 它是整个路由功能的“容器”或者说“路由器”。你需要在你的 React 应用的最顶层(通常是 App.jsreturn 语句中,或者 index.jsReactDOM.render 部分)用它包裹住所有需要路由功能的组件。
    • 原理: 它使用 HTML5 History API (PushState, ReplaceState) 来保持 UI 与 URL 的同步,是 Web 应用最常用的路由器。

    JavaScript

    // src/index.js (或者 App.js)
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter } from 'react-router-dom'; // 导入 BrowserRouter
    import App from './App';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <BrowserRouter> {/* 用 BrowserRouter 包裹你的应用 */}
          <App />
        </BrowserRouter>
      </React.StrictMode>
    );
  • Routes

    • 作用: 它是一个容器,里面包含了一组 Route 组件。Routes 的智能之处在于,它会遍历其内部的 Route 组件,然后只渲染第一个匹配当前 URL 的 Route
    • 特点: 解决了 v5 中 Switch 的痛点,更简洁。

    JavaScript

    // src/App.js
    import { Routes, Route } from 'react-router-dom';
    import HomePage from './pages/HomePage';
    import AboutPage from './pages/AboutPage';
    import ContactPage from './pages/ContactPage';
    import NotFoundPage from './pages/NotFoundPage';
    
    function App() {
      return (
        <Routes> {/* Routes 容器 */}
          <Route path="/" element={<HomePage />} /> {/* 匹配 "/" 渲染 HomePage */}
          <Route path="/about" element={<AboutPage />} /> {/* 匹配 "/about" 渲染 AboutPage */}
          <Route path="/contact" element={<ContactPage />} /> {/* 匹配 "/contact" 渲染 ContactPage */}
          <Route path="*" element={<NotFoundPage />} /> {/* 匹配所有未定义的路径,通常用于 404 页面 */}
        </Routes>
      );
    }
  • Route

    • 作用:

      定义一个路由规则。它有两个主要属性:

      • path:一个字符串,表示要匹配的 URL 路径。
      • element:当 path 匹配时,要渲染的 React 元素(通常是你的组件)。
    • 注意: v6 移除了 componentrender prop,统一使用 element prop。

    JavaScript

    <Route path="/products/:productId" element={<ProductDetail />} /> {/* 带有动态参数 */}
  • Link

    • 作用: 这是在 React 应用中进行页面导航的首选方式。它会渲染成一个 <a> 标签,但点击时不会触发浏览器进行完整的页面刷新,而是由 React Router 接管,进行客户端路由跳转。
    • 属性: 最常用的是 to,指定要跳转的路径。

    JavaScript

    import { Link } from 'react-router-dom';
    
    function Navigation() {
      return (
        <nav>
          <Link to="/">首页</Link>
          <Link to="/about">关于我们</Link>
          <Link to="/contact">联系方式</Link>
        </nav>
      );
    }

3. React Router v6+ 的核心 Hook

在函数组件中,我们通常使用 Hook 来获取路由相关的信息或进行编程导航。

  • useParams():获取 URL 中的动态参数

    • 作用: 当你的路由路径中包含动态部分(例如 /users/:id),useParams Hook 可以让你轻松地获取这些动态参数的值。

    • 例子:

      JavaScript

      // 定义路由:<Route path="/users/:userId" element={<UserProfile />} />
      import { useParams } from 'react-router-dom';
      
      function UserProfile() {
        const { userId } = useParams(); // URL 是 /users/123,userId 就会是 "123"
        return <h2>用户 ID: {userId}</h2>;
      }
  • useNavigate():进行编程式导航

    • 作用: 当你需要在事件处理器(如表单提交后)、useEffect 中或任何不能直接使用 Link 的地方进行页面跳转时,可以使用 useNavigate。它返回一个 Maps 函数。

    • 例子:

      JavaScript

      import { useNavigate } from 'react-router-dom';
      
      function LoginForm() {
        const navigate = useNavigate();
      
        const handleSubmit = (e) => {
          e.preventDefault();
          // ... 处理登录逻辑 ...
          const success = true; // 假设登录成功
          if (success) {
            navigate('/dashboard'); // 导航到仪表盘页面
            // navigate(-1); // 返回上一页
            // navigate('/some-path', { replace: true }); // 替换当前历史记录而不是推入新记录
          }
        };
      
        return (
          <form onSubmit={handleSubmit}>
            {/* ... 表单内容 ... */}
            <button type="submit">登录</button>
          </form>
        );
      }
  • useLocation():获取当前 URL 的信息

    • 作用: 返回一个 location 对象,包含当前 URL 的各种信息,如 pathname (路径), search (查询参数), hash (哈希值), state (通过 Maps 传递的状态)。

    • 例子:

      JavaScript

      import { useLocation } from 'react-router-dom';
      
      function CurrentPageInfo() {
        const location = useLocation();
        console.log(location.pathname); // 当前路径,如 "/about"
        console.log(location.search);   // 查询参数,如 "?name=Alice"
        console.log(location.state);    // navigate 传过来的 state
      
        return (
          <p>你当前在: {location.pathname}</p>
        );
      }
  • useOutlet():渲染嵌套路由的子组件

    • 作用: 在父级路由组件中,useOutlet(或直接使用 <Outlet /> 组件)用于渲染其匹配的子路由组件。这是实现嵌套路由的关键。

4. 嵌套路由:构建复杂布局

嵌套路由允许你将父组件的布局和子组件的特定内容结合起来。

  • 口述图示:嵌套路由的结构

    想象你有一个用户管理页面 /users。这个页面有导航栏和侧边栏,而它的子路由 /users/profile 和 /users/settings 只改变右侧的内容区域。

    // App.js (主路由文件)
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/users" element={<UserLayout />}> {/* 父级路由,渲染 UserLayout */}
        <Route index element={<UserDashboard />} /> {/* 默认子路由,当 path="/users" 时渲染 */}
        <Route path="profile" element={<UserProfile />} /> {/* 完整路径 /users/profile */}
        <Route path="settings" element={<UserSettings />} /> {/* 完整路径 /users/settings */}
      </Route>
      {/* ... 其他路由 ... */}
    </Routes>
    
    // UserLayout.js (父级路由组件)
    import { Outlet, Link } from 'react-router-dom';
    
    function UserLayout() {
      return (
        <div>
          <h1>用户管理</h1>
          <nav>
            <Link to="/users">仪表盘</Link> | {' '}
            <Link to="/users/profile">个人资料</Link> | {' '}
            <Link to="/users/settings">设置</Link>
          </nav>
          <hr />
          <div style={{ border: '1px solid gray', padding: '10px' }}>
            {/* Outlet 会在这里渲染匹配到的子路由组件 */}
            <Outlet />
          </div>
        </div>
      );
    }
    
    // UserDashboard.js, UserProfile.js, UserSettings.js 都是普通的 React 组件
  • 注意 index 路由: index 路由表示当父级路由路径完全匹配(没有子路径)时,默认渲染的子组件。

  • 子路由 path 子路由的 path 不需要以 / 开头,它会自动相对于父路由的 path。例如,path="profile" 意味着完整路径是 /users/profile

5. 重要的考虑和最佳实践

  • 404 Not Found 页面:

    • 使用 Routes 容器的最后一个 Route,将 path 设置为 * (星号,通配符)。它会匹配所有未被前面路由匹配的路径。
    • <Route path="*" element={<NotFoundPage />} />
  • 路由守卫(Authentication / Protected Routes):

    • React Router 本身不提供内置的路由守卫功能,但你可以通过element Prop 中嵌套一个高阶组件 (HOC) 或一个条件渲染组件来实现。

    • 例如:

      JavaScript

      // ProtectedRoute.js
      import { Navigate } from 'react-router-dom';
      import { useAuth } from './authContext'; // 假设你有一个认证 context
      
      function ProtectedRoute({ children }) {
        const { isAuthenticated } = useAuth(); // 检查用户是否认证
        if (!isAuthenticated) {
          return <Navigate to="/login" replace />; // 未认证则重定向到登录页
        }
        return children; // 认证通过则渲染子组件
      }
      
      // App.js
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        <Route path="/dashboard" element={
          <ProtectedRoute>
            <DashboardPage />
          </ProtectedRoute>
        } />
      </Routes>
  • 使用 useResolvedPathuseMatch 进行更高级的路径匹配和解析。

  • 性能优化:

    结合

    React.lazy

    Suspense

    对路由组件进行

    懒加载

    ,可以显著提升应用的首次加载速度。

    JavaScript

    import React, { lazy, Suspense } from 'react';
    import { Routes, Route } from 'react-router-dom';
    
    const HomePage = lazy(() => import('./pages/HomePage'));
    const AboutPage = lazy(() => import('./pages/AboutPage'));
    
    function App() {
      return (
        <Suspense fallback={<div>加载中...</div>}>
          <Routes>
            <Route path="/" element={<HomePage />} />
            <Route path="/about" element={<AboutPage />} />
          </Routes>
        </Suspense>
      );
    }

总结与展望

React Router 是构建现代单页应用的基石。它让你的应用在没有页面刷新的情况下实现复杂的导航,大大提升了用户体验。

  • 核心组件: BrowserRouter (路由器容器), Routes (路由集合), Route (单个路由规则), Link (声明式导航)。
  • 核心 Hook: useParams (获取路径参数), useNavigate (编程导航), useLocation (获取当前路由信息), useOutlet (渲染嵌套子路由)。
  • 最佳实践: 拥抱 v6 的简洁 API,合理使用嵌套路由,利用 * 路径处理 404,通过 lazy/Suspense 进行代码分割,并考虑如何实现路由守卫。

掌握 React Router,你就能搭建起复杂且用户体验极佳的单页应用。多动手实践,尝试不同的路由场景,你很快就能成为路由高手!

继续加油!你的前端技能树正在茁壮成长!

One Day We Will All Be Famous!