React Router 中的滚动恢复

发布: (2026年1月17日 GMT+8 17:30)
5 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容,我将按照要求将其译成简体中文并保留原有的格式、Markdown 语法以及技术术语。

介绍

在使用 React Router 构建单页应用(SPA)时,几乎立刻会出现一个常见的用户体验问题:导航会更改 URL,但页面不会滚动到顶部。这违背了用户的预期,尤其是在博客、文档或仪表盘等内容丰富的页面上。

传统多页网站 vs. 单页应用(SPA)

传统多页网站单页应用(SPA)
导航触发完整页面重新加载在客户端完成
DOM 重新加载
浏览器滚动重置自动回到 (0, 0)默认保持原位置

因此,从 /blog/about 导航时,页面会停留在滚动到一半的位置。

简单且正确的实现

import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";

export function ScrollToTop() {
  const { pathname } = useLocation();

  useLayoutEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
}
  • useLocation() 提供当前的 location 对象。
  • pathname 仅在路由变化时改变(除非你将查询参数或哈希也包含在内,否则在 query 参数或 hash 变化时不会改变)。
  • useLayoutEffect 在浏览器绘制之前运行,防止出现可见的跳动/闪烁。

提示: 对布局相关的副作用(如滚动、焦点或 DOM 测量)使用 useLayoutEffect

组件的挂载位置

import { BrowserRouter } from "react-router-dom";
import { ScrollToTop } from "./ScrollToTop";

<BrowserRouter>
  <ScrollToTop />
  {/* ...your routes */}
</BrowserRouter>
  • 在路由器的根部一次挂载它。
  • 不要将它放在单独的页面中或单独包装路由。

处理查询参数

如果您希望在查询字符串更改时重置滚动位置:

import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";

export function ScrollToTop() {
  const { pathname, search } = useLocation();

  useLayoutEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname, search]);

  return null;
}

保持锚点(hash)导航

当 URL 包含哈希(例如 /docs#getting-started)时,上面的组件会覆盖浏览器默认的锚点滚动行为。通过忽略哈希变化来修复:

import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";

export function ScrollToTop() {
  const { pathname, hash } = useLocation();

  useLayoutEffect(() => {
    if (hash) return; // let the browser handle anchor scrolling
    window.scrollTo(0, 0);
  }, [pathname, hash]);

  return null;
}

注意: 浏览器会自动记住返回/前进导航时的滚动位置。此组件可能会干扰该行为,因此最好仅在前向导航时使用,或在特定路由上禁用。

iOS 与替代滚动方法

在某些 iOS 设备上,window.scrollTo 可能因动量滚动而失效。更安全的后备方案:

document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;

仅在遇到问题时使用该后备方案。

在自定义容器内滚动

如果你的应用在特定元素内部滚动,请直接定位该元素:

document.getElementById("scroll-root")?.scrollTo(0, 0);

Development quirks

  • 在开发模式下,useLayoutEffect 因 React 的严格模式而运行两次。这 不会 影响生产环境,可忽略。
  • 该 effect 仅在导航时运行,执行一次同步操作,并且导致零次重新渲染。

何时 不使用 ScrollToTop

在某些 UI 模式下,保留滚动位置更为合适:

  • 无限滚动页面
  • 聊天应用
  • 带自动保存的表单
  • 基于地图的界面

在这些情况下,禁用自动滚动到顶部可以提升用户体验。

完整示例(包括 hash 处理)

import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";

export function ScrollToTop() {
  const { pathname, hash } = useLocation();

  useLayoutEffect(() => {
    if (hash) return;
    window.scrollTo({ top: 0, left: 0, behavior: "instant" });
  }, [pathname, hash]);

  return null;
}

结论

滚动恢复不是可选的 UI 打磨——它是核心的导航期望。这个小组件:

  • 修复了 SPA 的根本缺陷
  • 提升感知性能
  • 让你的应用感觉“原生”

如果你使用 React Router 且没有此行为,用户会注意到——即使他们没有告诉你。

Back to Blog

相关文章

阅读更多 »

React 应用基础

介绍 今天我们将探讨在创建 React 应用时可见的文件和文件夹的原因和用途。 !React app structure https:/...