2025年React面试题———react-router面试题整理

9 阅读11分钟

一、基础概念类

1. React Router 的核心作用是什么?

React Router 是 React 生态中用于 SPA(单页应用)路由管理 的库,核心作用:

  • 实现 URL 与组件的映射关系(路由匹配);
  • 提供无刷新的页面跳转(客户端路由);
  • 支持动态路由、嵌套路由、路由鉴权等高级功能;
  • 维护路由状态(参数、历史记录),实现组件间路由相关通信。

2. BrowserRouter 和 HashRouter 的区别?

两者是 React Router 的顶层路由容器,核心差异在于 URL 形式 和 底层实现原理

对比维度BrowserRouter(history 模式)HashRouter(hash 模式)
URL 形式http://xxx.com/user/123(无 #http://xxx.com/#/user/123(带 #
底层原理基于 HTML5 的 history API(pushState/replaceState基于 URL 中的 hash(锚点),依赖 hashchange 事件
后端配置需要配置(Nginx/Apache),否则刷新 404无需后端配置,刷新不会 404
兼容性支持 HTML5 的现代浏览器(IE10+)兼容性更强(支持 IE8+)
场景正式生产环境(需要后端配合)快速开发、静态页面部署(无需后端)

注意:BrowserRouter 刷新 404 原因:URL 被后端解析为真实接口路径,需配置后端将所有路由转发到 index.html(如 Nginx 的 try_files $uri $uri/ /index.html;)。

3. Link、NavLink、a 标签的区别?

标签核心作用关键差异场景
<a>原生页面跳转会刷新页面,破坏 SPA 无刷新特性跳转到外部网站
<Link>React Router 内部跳转无刷新跳转,通过 to 属性指定目标路由普通内部页面跳转
<NavLink>带激活状态的内部跳转继承 <Link> 功能,可通过 className/style 配置激活样式导航菜单(如侧边栏)

代码示例(v6):

jsx

// NavLink 激活状态配置
<NavLink
  to="/home"
  // 函数形式:接收 isActive 参数,动态设置类名
  className={({ isActive }) => isActive ? 'nav-active' : 'nav-normal'}
>
  首页
</NavLink>

// 样式效果:激活时添加 nav-active 类
.nav-active { color: red; font-weight: bold; }

4. 什么是动态路由?如何实现?

动态路由:URL 中包含可变参数的路由(如 /user/123/post/456),用于匹配一类页面(如不同用户的详情页)。

实现步骤(v6):

  1. 路由定义时,用 :参数名 声明动态参数;
  2. 组件中通过 useParams() 钩子获取参数。

代码示例:

jsx

// 1. 路由配置(动态参数 :id)
<Routes>
  <Route path="/user/:id" element={<UserDetail />} />
</Routes>

// 2. 组件中获取参数
import { useParams } from 'react-router-dom';

function UserDetail() {
  const { id } = useParams(); // 解构动态参数 id
  return <div>用户 ID:{id}</div>;
}

5. v6 中 <Routes> 和 v5 中 <Switch> 的区别?

v6 用 <Routes> 替代了 v5 的 <Switch>,核心差异:

特性v5 <Switch>v6 <Routes>
匹配规则模糊匹配(需手动加 exact 实现精确匹配)默认 精确匹配(无需 exact),模糊匹配需用 *
路由嵌套需手动嵌套 <Route>支持扁平化配置,配合 <Outlet> 实现嵌套路由
性能遍历所有子路由,找到第一个匹配项优化匹配逻辑,只渲染匹配的路由组件
子路由渲染无默认出口,需手动渲染子组件必须通过 <Outlet> 渲染子路由组件

示例对比:

jsx

// v5(需 exact 实现精确匹配)
<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/user" component={UserList} />
  <Route path="/user/:id" component={UserDetail} />
</Switch>

// v6(默认精确匹配,无需 exact)
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/user" element={<UserList />} />
  <Route path="/user/:id" element={<UserDetail />} />
</Routes>

二、进阶用法类

6. 如何获取路由参数?(params、search、state)

React Router 支持 3 类路由参数,适用场景不同:

参数类型获取方式URL 表现适用场景
paramsuseParams()/user/:id(路径参数)唯一标识(如用户 ID)
searchuseSearchParams()?name=xxx&age=20(查询参数)筛选、分页(如列表筛选)
stateuseLocation().state隐藏在历史记录中(URL 无显示)传递敏感数据、临时状态(如登录后回跳路径)

代码示例:

jsx

// 1. params(动态路径参数)
const { id } = useParams();

// 2. search(查询参数)
const [searchParams, setSearchParams] = useSearchParams();
const name = searchParams.get('name'); // 获取
setSearchParams({ name: 'new', age: 25 }); // 修改

// 3. state(历史记录状态)
// 跳转时传递
<Link to="/user" state={{ from: '/home' }} />
// 或编程式导航
navigate('/user', { state: { from: '/home' } });
// 组件中获取
const { state } = useLocation();
const from = state?.from;

7. 编程式导航如何实现?(v5 vs v6)

编程式导航指通过代码(而非 <Link> 标签)实现页面跳转,核心差异在于 v6 用 useNavigate 替代了 v5 的 useHistory

v6 实现(useNavigate):

jsx

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

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

  const handleGo = () => {
    navigate('/home'); // 跳转到 /home(push 模式,新增历史记录)
    navigate(-1); // 后退 1 步(类似 history.back())
    navigate(1); // 前进 1 步(类似 history.forward())
    navigate('/user', { replace: true }); // replace 模式(替换当前历史记录,无法回退)
    navigate('/user', { state: { id: 123 } }); // 传递 state
  };

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

v5 实现(useHistory):

jsx

import { useHistory } from 'react-router-dom';

const history = useHistory();
history.push('/home'); // 跳转
history.replace('/user'); // 替换
history.goBack(); // 后退
history.push('/user', { id: 123 }); // 传递 state

8. 如何实现嵌套路由?(v6 核心)

嵌套路由指路由层级嵌套(如 /dashboard/settings 是 /dashboard 的子路由),v6 通过 <Outlet> 组件实现子路由渲染,比 v5 更简洁。

实现步骤:

  1. 父路由配置时,不指定 path 或指定父路径,子路由通过 children 嵌套;
  2. 父组件中用 <Outlet> 作为子路由的 “出口”(子路由组件会渲染在 <Outlet> 位置)。

代码示例:

jsx

// 1. 路由配置(嵌套结构)
<Routes>
  {/* 父路由:路径 /dashboard,子路由嵌套在 children 中 */}
  <Route path="/dashboard" element={<Dashboard />}>
    <Route index element={<DashboardHome />} /> {/* 默认子路由(/dashboard) */}
    <Route path="settings" element={<DashboardSettings />} /> {/* 子路由(/dashboard/settings) */}
    <Route path="profile" element={<DashboardProfile />} /> {/* 子路由(/dashboard/profile) */}
  </Route>
</Routes>

// 2. 父组件 Dashboard(用 <Outlet> 渲染子路由)
function Dashboard() {
  return (
    <div>
      <h1>仪表盘</h1>
      <nav>
        <Link to="settings">设置</Link> {/* 相对路径(无需写 /dashboard) */}
        <Link to="profile">个人资料</Link>
      </nav>
      <Outlet /> {/* 子路由组件会渲染在这里 */}
    </div>
  );
}

注意:子路由的 path 是相对路径(无需带父路径),<Link> 中 to 也支持相对路径。

9. 如何实现路由鉴权(登录守卫)?

路由鉴权指:未登录用户访问需要权限的页面时,自动跳转到登录页;已登录用户则正常访问。v6 推荐用 <Outlet> + 鉴权组件实现。

实现步骤:

  1. 创建鉴权组件 RequireAuth,判断登录状态;
  2. 需鉴权的路由嵌套在 RequireAuth 组件中;
  3. 未登录时跳转到登录页,并记录回跳路径。

代码示例:

jsx

// 1. 鉴权组件 RequireAuth
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useSelector } from 'react-redux'; // 假设用 Redux 存储登录状态

function RequireAuth() {
  const isLogin = useSelector(state => state.user.isLogin); // 获取登录状态
  const location = useLocation(); // 获取当前访问路径

  // 未登录:跳转到登录页,传递回跳路径(state.from)
  if (!isLogin) {
    return <Navigate to="/login" state={{ from: location.pathname }} replace />;
  }

  // 已登录:渲染子路由(通过 Outlet)
  return <Outlet />;
}

// 2. 路由配置(嵌套鉴权)
<Routes>
  <Route path="/login" element={<Login />} />
  {/* 需鉴权的路由:嵌套在 RequireAuth 中 */}
  <Route element={<RequireAuth />}>
    <Route path="/dashboard" element={<Dashboard />} />
    <Route path="/profile" element={<Profile />} />
  </Route>
</Routes>

// 3. 登录组件:登录成功后回跳
function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from || '/'; // 回跳路径(默认首页)

  const handleLogin = () => {
    // 登录逻辑(如调用接口、存储 token)
    navigate(from, { replace: true }); // 回跳原路径
  };

  return <button onClick={handleLogin}>登录</button>;
}

10. 如何配置 404 页面?

404 页面用于匹配所有未定义的路由,v6 中用 path="*" 实现(模糊匹配所有路径)。

代码示例:

jsx

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/user/:id" element={<UserDetail />} />
  {/* 404 页面:放在最后,匹配所有未定义路由 */}
  <Route path="*" element={<NotFound />} />
</Routes>

// NotFound 组件
function NotFound() {
  return (
    <div>
      <h1>404 Not Found</h1>
      <Link to="/">返回首页</Link>
    </div>
  );
}

11. 路由懒加载的实现方式?

路由懒加载指:组件在路由匹配时才动态加载(而非初始加载时全部加载),优化首屏加载速度。核心依赖 React.lazy + Suspense

实现步骤:

  1. 用 React.lazy(() => import('组件路径')) 动态导入组件;
  2. 用 Suspense 包裹懒加载路由,指定加载时的 fallback(占位符)。

代码示例:

jsx

import { Routes, Route, Suspense } from 'react-router-dom';
import React from 'react';

// 懒加载组件(动态导入)
const User = React.lazy(() => import('./User'));
const Dashboard = React.lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}> {/* 加载时显示占位符 */}
      <Routes>
        <Route path="/user" element={<User />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  );
}

注意:Suspense 需包裹在懒加载路由的父级(可全局包裹,也可局部包裹),否则会报错。

12. 如何监听路由变化?

监听路由变化可用于:页面埋点、数据刷新、滚动重置等场景,核心通过 useLocation + useEffect 实现。

代码示例:

jsx

import { useLocation, useNavigate } from 'react-router-dom';

function Demo() {
  const location = useLocation();
  const navigate = useNavigate();

  // 监听路由变化(pathname 或 search 改变时触发)
  useEffect(() => {
    console.log('当前路由:', location.pathname);
    // 场景1:路由变化时重置滚动位置
    window.scrollTo(0, 0);
    // 场景2:埋点上报
    // reportTrack('page_view', { path: location.pathname });
  }, [location]); // 依赖 location(路由变化时 location 会更新)

  return <div>监听路由变化示例</div>;
}

三、原理类

13. 前端路由(SPA 路由)的实现原理是什么?

SPA(单页应用)的核心是 “无刷新跳转”,前端路由通过 URL 变化触发组件更新,无需向后端请求新页面,原理分两类:

1. Hash 模式(HashRouter):

  • 基于 URL 中的 hash(如 #/user),hash 变化不会触发页面刷新,也不会发送请求到后端;
  • 通过监听 window 的 hashchange 事件,捕获 hash 变化,然后匹配对应的组件渲染。

2. History 模式(BrowserRouter):

  • 基于 HTML5 的 history API(pushStatereplaceState),可修改 URL 但不刷新页面;
  • 通过监听 window 的 popstate 事件(监听浏览器前进 / 后退按钮),配合 history API 手动修改 URL,实现路由切换;
  • 缺点:URL 变化时会触发后端请求,需后端配置所有路由转发到 index.html

核心流程:

  1. 监听 URL 变化(hashchange 或 popstate);
  2. 解析 URL 路径,匹配预定义的路由规则;
  3. 渲染匹配的组件,隐藏不匹配的组件。

14. React Router 的核心原理是什么?

React Router 本质是  “组件化路由解决方案” ,核心基于 React 的上下文(Context)和 Hooks,流程如下:

  1. 路由上下文(RouterContext) :顶层 BrowserRouter/HashRouter 会创建路由上下文,存储 historylocation 等核心对象,子组件通过 Hooks(如 useLocationuseNavigate)获取这些对象;
  2. 路由匹配Routes 组件遍历子 Route,根据当前 location.pathname 匹配对应的 Route,只渲染匹配的 element
  3. 导航控制Link/useNavigate 通过 history 对象修改 URL(pushState/hash),触发 location 更新;
  4. 组件更新location 是上下文状态,更新后会触发依赖它的组件(如 RoutesuseLocation 所在组件)重新渲染,实现路由切换。

15. history 库的核心作用是什么?

React Router 依赖 history 库(而非原生 history API),核心作用:

  • 封装 hash 和 history 两种模式的路由操作(统一 API);
  • 提供 pushreplacego 等方法,用于修改 URL 和历史记录;
  • 维护 location 对象(包含 pathnamesearchstate 等),并在 URL 变化时通知 React Router 更新组件;
  • 处理浏览器前进 / 后退按钮的事件监听(popstate/hashchange)。

四、v5 vs v6 核心差异(高频考点)

16. React Router v5 和 v6 的主要区别有哪些?

特性v5v6
顶层容器<BrowserRouter> 直接包裹路由无变化,但路由配置需嵌套在 <Routes> 中
路由匹配容器<Switch><Routes>(默认精确匹配,性能优化)
路由组件属性component/renderelement(接收 JSX 元素,而非组件引用)
嵌套路由手动嵌套 <Route>,需写完整路径扁平化配置(children 嵌套),配合 <Outlet> 渲染子路由
导航 HooksuseHistoryuseNavigate(API 更简洁)
NavLink 激活activeClassName/activeStyle需通过 className/style 接收函数(({ isActive }) => {}
路由匹配规则模糊匹配(需 exact默认精确匹配,模糊匹配需用 *(如 /user/*
路由配置仅支持 JSX 配置支持 JSX 配置和 useRoutes(对象配置)

关键代码对比:

jsx

// v5 路由配置
<BrowserRouter>
  <Switch>
    <Route exact path="/" component={Home} />
    <Route path="/user" component={UserList} />
    <Route path="/user/:id" render={(props) => <UserDetail {...props} />} />
  </Switch>
</BrowserRouter>

// v6 路由配置
<BrowserRouter>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/user" element={<UserList />} />
    <Route path="/user/:id" element={<UserDetail />} />
  </Routes>
</BrowserRouter>

五、实战场景类

17. 如何处理路由切换时的组件卸载(如数据清理)?

路由切换时,当前组件会卸载,需清理副作用(如定时器、订阅、请求取消),核心通过 useEffect 的返回函数实现。

代码示例:

jsx

import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

function UserDetail() {
  const navigate = useNavigate();
  let timer;

  useEffect(() => {
    // 副作用:开启定时器
    timer = setInterval(() => {
      console.log('定时器运行中...');
    }, 1000);

    // 组件卸载时清理副作用(路由切换会触发卸载)
    return () => {
      clearInterval(timer); // 清除定时器
      // 其他清理:取消请求、取消订阅等
      // abortController.abort();
    };
  }, []);

  return <div>用户详情页</div>;
}

18. 如何实现路由拦截(如权限粒度控制)?

除了登录鉴权,还可能需要更细粒度的权限控制(如角色权限:管理员可访问,普通用户不可),核心通过 “鉴权组件 + 角色判断” 实现。

代码示例:

jsx

// 角色鉴权组件(仅管理员可访问)
function RequireAdmin() {
  const { userRole } = useSelector(state => state.user); // 角色:admin/normal
  const location = useLocation();

  if (userRole !== 'admin') {
    return <Navigate to="/403" replace />; // 无权限跳 403
  }

  return <Outlet />;
}

// 路由配置
<Routes>
  <Route element={<RequireAuth />}> {/* 登录鉴权 */}
    <Route path="/dashboard" element={<Dashboard />} />
    {/* 角色鉴权:仅管理员可访问 /admin */}
    <Route element={<RequireAdmin />}>
      <Route path="/admin" element={<AdminPanel />} />
    </Route>
  </Route>
  <Route path="/403" element={<Forbidden />} /> {/* 无权限页面 */}
</Routes>

总结

React Router 面试核心围绕 v6 新特性<Routes><Outlet>useNavigate)、路由匹配规则鉴权 / 懒加载 / 嵌套路由 等实战场景,以及 前端路由原理(hash/history 模式)。备考时需重点掌握 v6 与 v5 的差异,结合代码示例理解核心用法,同时理清底层原理(URL 监听、组件渲染流程)。