React Router 中的滚动恢复
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 且没有此行为,用户会注意到——即使他们没有告诉你。