🔥React Router完全解析,花8min读完这一篇,立刻成为高手🔥

239 阅读9分钟

前言

在上一期我们介绍了historyhash两种前端路由常用的模式,它们能够使得我们在不重载刷新页面的情况下,实现页面的更新,让用户体验又向上拔高了一个层级,让服务器也没有以前忙的找不着北了,既然它这么有用,我们总不能用纯生JS一直用吧?要知道现在的主流开发都是用VueReact这种框架的,那么这些框架中有没有类似的功能呢?聪明如你当然知道了,react中也存在类似的功能,那么今天我们就来介绍一下react router!

d2c45aef645a46bc99dbc01e91c4eb95.gif

什么是 React Router ?

所以什么是React Router呢?

React Router 是 React 生态中最流行的客户端路由(Client-Side Routing) 管理库,用于构建单页应用(SPA, Single Page Application) 。它允许我们在不刷新整个页面的情况下,动态切换不同的视图(组件),并保持URL与界面同步。

它的核心功能包括:

  • URL 映射到组件:根据浏览器地址栏的路径,渲染对应的 React 组件。
  • 导航控制:提供 <Link> 和 useNavigate 等方式实现页面跳转。
  • 动态路由:支持参数化路径(如 /users/:id),便于数据驱动视图。
  • 嵌套路由:允许页面布局分层,提高代码复用性。

如果说history/hash让一般的Web项目有了让SPA展示多页内容的能力,那么React Router就是让React项目也有了这个能力。

React Router 安装

首先我们要知道它并不是react自带的,react router是一个第三方库,所以即使你初始化了一个react文件,你仍然需要在项目文件的根目录下安装这个库才能使用:

npm install react-router-dom

React Router 基本用法

接下来就是它的基础用法了:

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/users/:id" element={<UserProfile />} />
      </Routes>
    </Router>
  );
}

我们来逐一介绍BrowserRouter, Routes, Route.

BrowserRouter

BrowserRouter可以提供基于HTML5 History API的路由容器:

  • 它可以包裹整个应用,使 React Router 的功能(如导航、路由匹配)生效。
  • 它使用 HTML5 的 pushState 和 popState 来管理 URL,确保页面无刷新跳转。
  • 当 URL 变化时,自动触发重新渲染匹配的组件。

也就是说,想让下面的路由都能享受到便捷的功能,就都得加入BrowserRouter的麾下。它就像个基石一样,没有它就是不行。

image.png

Routes

Routes是一个规则匹配容器,它会包裹一组 <Route>,负责根据当前 URL 选择最优匹配的路由

简而言之,当你在这一组<Route>中输入url来回切换时,是Routes在识别并帮你找到对应的<Route>展现在页面上。

当然,<Route>必须全部被包裹在Routes中才能享受这个功能。

image.png

Route

Route可以定义路由规则,将 URL 路径path)与 要渲染的组件element)关联。

image.png

  • Route支持:

    • 静态路径(如 /about
    • 动态路径参数(如 /users/:id
    • 嵌套路由(通过父路由的 <Outlet> 渲染子路由)
    • 默认路由index 属性)

接下来我们将介绍这些。

嵌套路由

React Router v6 中,嵌套路由(Nested Routes) 允许你将路由逻辑分层组织,实现复杂的页面布局(如带有共享 Header/Sidebar 的多级页面)。

比如下面这个例子:

<Routes>
  <Route path="/" element={<Layout />}>
    <Route path="home" element={<Home />} />          
    <Route path="about" element={<About />} />  
    <Route path="users" element={<Users />}>
      <Route path=":id" element={<UserDetail />} />  
    </Route>
  </Route>
</Routes>

image.png

一般情况下<Route>都是单标签,在这里我们可以看到 <Route path="/" element={<Layout />}></Route>成了双标签,在标签里面的<Route>全部成了它的子路由,可以实现共享<Layout />组件,这样无论怎么跳转,页面都会有一个<Layout />组件了。

那么父元素的组件是如何将子元素的组件嵌入到自己身上的?

那就要说到<Outlet />

<Outlet />

<Outlet /> 的作用:

  • 占位符组件:标记子路由内容应该渲染在父路由的哪个位置(类似 Vue 的 <router-view> 或 Angular 的 <router-outlet>)。
  • 动态插入:当 URL 匹配嵌套路由时,React Router 会用子路由的 element 替换 <Outlet />

我们就写上面的<Layout />组件吧:

// 父路由组件 (Layout.jsx)
import { Outlet } from 'react-router-dom';

function Layout() {
  return (
    <div>
      <header>Shared Header</header>
      <main>
        {/* 子路由的内容会渲染在这里 */}
        <Outlet />
      </main>
      <footer>Shared Footer</footer>
    </div>
  );
}

我们看到了<Outlet />,这就是占位符,<Route path="/" element={<Layout />}></Route>中的各个子组件都会渲染在<Outlet />这里。

在这里你有没有想过,当我们直接访问了父组件的时候,会不会展示子组件呢?

每个子组件都对应着一个地址,但我们访问的是:localhost:5173/,这个时候会有子组件显示吗?

答案是不会,但我们可以加一个属性index,选中一个默认子路由。

Route 的index属性

这个属性用于选定默认子路由,能让父组件渲染的同时,让这一个子组件也跟着渲染。

<Routes>
  <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />      
    {/*可以替代path,毕竟默认访问就可以展示这个组件,再加个path也没什么必要了,当然你想加一个path也可以*/}    
    <Route path="about" element={<About />} />  
    <Route path="users" element={<Users />}>
    <Route path=":id" element={<UserDetail />} />  
    </Route>
  </Route>
</Routes>

这个时候当我们访问父路由的时候,子路由默认渲染的就是<Home />组件。

导航与跳转

说了这么多路由了,那我们不能总是在页面上输入路由实现跳转吧?那样真的就会太原始了hhhhhh,所以我们接下来继续看 跳转的方法!(学无止境啊真是~)

005XSXmNly1hejr8dcehsj30ud0u0n0t.jpg

声明式导航

<Link to="path"> :用于页面跳转,不会刷新整个页面(SPA 路由切换)。

<NavLink> :是 <Link> 的增强版,可以自动添加 active 类名(用于高亮当前选中项)。

比如我们可以在Layout中这么写:

// 父路由组件 (Layout.jsx)
import { Outlet } from 'react-router-dom';

function Layout() {
  return (
    <div>
      <header>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/users">Users</Link>
        <Link to="/:id">userDetail</Link>
        <NavLink to="/about" style={({ isActive }) => ({color: isActive ? "red" : "black",})}>
            About
        </NavLink>
      </nav>
      </header>
      <main>
        {/* 子路由的内容会渲染在这里 */}
        <Outlet />
      </main>
      <footer>Shared Footer</footer>
    </div>
  );
}

编程式导航

useNavigate() :它为 React Router v6 提供的一个 Hook,用于在函数组件中以编程方式控制导航(如提交表单后跳转)

它是这么用的:

import { useNavigate } from "react-router-dom";

function MyComponent() {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate("/target-path"); // 跳转到指定路径
  };

  return <button onClick={handleClick}>跳转</button>;
}

它和<Link>的设计理念是不同的,<Link>是一个组件,主要是用户自己想要跳转的时候就可以点击<Link>跳转,而navigate经常用于函数的异步操作或者表单的提交中,通常是由代码逻辑触发的,所以两者的适用场景就不同。

注意:navigate()里面的地址中+/,就会从应用的根路径解析,不加/,就会从当前路径解析。

image.png

小插曲::id

这是动态路径,这里可以是任何值,比如这个例子: <Route path=":id" element={<UserDetail />} />

如果我的根路径是localhost:5173,那么我可以访问localhost:5173/Skye,localhost:5173/12333......

这个参数一般都是需要我们在组件中获取拿来用的,我们可以用useParams

useParams

React Router v6 中,useParams 是一个 Hook,用于获取当前路由的动态参数(即 URL 中的占位符变量)。它常用于从 URL 中提取值(如 ID、用户名等),以便在组件中动态渲染内容或发起数据请求。

假如我们在上面的<UserDetail />中用来获取一些信息:

import { useEffect } from "react";

function UserDetail() {
  const { userId } = useParams();

  useEffect(() => {
    if (userId) {
      fetch(`/api/users/${userId}`)
        .then((res) => res.json())
        .then((data) => console.log(data));
    }
  }, [userId]);

  return <div>Loading user data...</div>;
}

懒加载/路由守卫

最后!我们说一点高级的!

4a36acaf2edda3cc7cd9627d71bc2e01213fb80e346a.webp

懒加载

引入页面组件后,页面组件全部会加载,如果项目过大,比如30个组件同时加载,会影响性能,让你的加载很慢,这个时候我们就可以使用懒加载了。

用法:

import { lazy, Suspense } from 'react'
{/*把import全部改为:👇*/}
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const NotFound = lazy(() => import('./pages/NotFound'))
const Pay = lazy(() => import('./pages/Pay'))
const Login = lazy(() => import('./pages/Login'))
{/*在组件中该怎么用就怎么用*/}

function App() {


  return (
    <>
      <Router>
        <Navigation />
        <Suspense fallback={<div>loading</div>}>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            {/* 需要校验权限的路由 */}
            <Route path="/pay" element={
              <ProtectRoute>
                <Pay />
                <div>123</div>
                <div>456</div>
              </ProtectRoute>
            } />
            <Route path='*' element={<NotFound />} />
            <Route path="/login" element={<Login />} />
          </Routes>
        </Suspense>
      </Router>
    </>
  )
}

export default App
  • React.lazy 接受一个返回 import() 的函数。
  • import() 是动态导入语法(返回 Promise),会在组件首次渲染时加载对应的模块。

它必须要搭载<Suspense>使用,Suspense意为悬停,加载是异步的,在没加载完成前,就会显示 Suspensefallback(备用组件,目标显示失败或加载中的时候会显示),fallback可以接收一个元素,也能接收一个组件。

路由守卫

想一想有的网站是不是登录后才能评论点赞?在你想点赞或者评论的时候,会弹出登录的窗口,没错,路由守卫就是干这个的。

在用户做了一些没权限的操作需要验证是否有权限的时候,就用上它了,比如:没登录就要评论,就用守卫拦截了,把它重定向到登录页面。

用这个例子来看:

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

要访问/pay路由时,会加载<ProtectRoute>组件,其他组件都是它的子组件,我们要完成一个验证功能,比如我们利用localStorage进行验证,如果里面有特定值,我们就返回子组件,没有就重定向到登录页面:

import {
    Navigate,
    useLocation
} from 'react-router-dom';
// 鉴权组件

const ProtectedRoute = (props) => {
    const { children } = props; // children是ProtectedRoute包裹的所有子组件,react就是这么设计的
    const { pathname } = useLocation(); // 获取当前路径
    const isLogin = localStorage.getItem('isLogin') === 'true';
    if (!isLogin) {
        console.log('未登录');
        return (
            <Navigate to="/login" state={{ from: pathname }} />
            // 重定向到登录页
        )
    }

    return children  // 返回子组件

}

export default ProtectedRoute

<Navigate>

<Navigate>通过 JSX 组件触发跳转(类似重定向),它会在组件渲染时自动触发导航。

作用和 useNavigate基本一致,都是为了跳转而用,用的时候区分一下需求和场景即可:

  • <Navigate>

    • 需要在 渲染阶段 直接跳转(如权限拦截、404 处理)。
    • 逻辑简单,直接依赖条件渲染。
  • useNavigate

    • 需要 手动触发跳转(如按钮点击、API 调用后)。
    • 需要更复杂的导航逻辑(如跳转前确认、动态路径计算)。

总结

OK,这一期就是这样了,我们从Router一路讲到路由守卫,当你读完这篇文章,我相信你一定对于react router有了很深的理解了,我们只要记住Router的设计都是为了热更新和用户体验而生就够了,Router能实现不刷新的跳转页面,会节省性能。