使用 React Router 6 导航时恢复滚动位置

Jan*_*ard 13 javascript reactjs react-router-dom

如何设置 React Router 6 以在导航和刷新浏览器窗口时恢复滚动位置?

React Router 5 有一个关于滚动恢复的页面,但我在v6 的文档中找不到任何有关滚动的内容,所以我猜你应该用另一个包自己处理这个问题。很公平,但我找不到任何与 React Router 6 兼容的东西。

react-scroll-restorationoaf-react-router需要v5。(oaf-react-router 确实列出了它支持 v6,但基本使用代码示例与 v6 不兼容,相关问题 #210仍然处于开放状态。)

Gatsby 和 Next.js 支持开箱即用的滚动恢复,但我看起来没有一个可以直接使用的整齐打包的包。

这个带有服务器端渲染页面的小演示应用程序可以满足我的需求。当来回导航并刷新浏览器窗口时,滚动位置会恢复。

这是使用 React Router 6 的同一个应用程序,其中滚动位置不会保存和恢复,但实际上在页面之间重用。通常的解决方法是每当导航页面时滚动到顶部,但我对这种行为不感兴趣。

编辑:React Query 写道,滚动恢复的问题是页面正在重新获取数据,这意味着如果用于渲染页面的数据存在,则滚动恢复就可以工作。我无法确认这一点,因为我的小型 React Router 6 应用程序即使根本没有进行任何数据获取也存在问题。我觉得为了让它发挥作用,我缺少一些小东西。

Rant:令我感到非常惊讶的是,这个问题的典型答案是在导航时打电话window.scrollTo(0, 0)。这仅解决了页面之间滚动位置转移的问题。当滚动位置未恢复时,在页面之间导航时的用户体验会严重恶化。我想这就是弹出窗口变得如此流行的部分原因,但它们带来了一系列其他用户体验问题,所以我真的想避免使用它们。

hev*_*ev1 6

ScrollRestoration组件正是处理这个问题的。它需要使用数据路由器,例如通过调用创建的数据路由器createBrowserRouter(建议所有新的 React Router Web 项目使用)。

该组件将在加载程序完成后模拟浏览器在位置更改时的滚动恢复,以确保滚动位置恢复到正确的位置,即使跨域也是如此。

要使用它,只需在应用程序根组件中渲染一次即可:

import { ScrollRestoration } from "react-router-dom";
function App() {
    return <>
        <div>Some content...</div>
        <ScrollRestoration/>
    </>;
}
Run Code Online (Sandbox Code Playgroud)


Jan*_*ard 1

我知道我已经发布了一个答案,但我最终得到了一个不同的解决方案,我认为它值得一个单独的答案。

我必须意识到的第一件事是StackBlitz 和 CodeSandbox 都有一些会破坏滚动恢复的自定义路由处理。在单独的窗口中打开演示应用程序时,此问题仍然存在。如果应用程序在正常环境中运行,则使用浏览器的后退和前进按钮在页面之间导航时会恢复滚动位置。

这两个问题仍然存在:

  1. 使用应用程序中的链接导航时,滚动位置在页面之间保持不变。从技术上讲,当使用 React Router<Link to="..."/>navigate(...). 页面应该滚动到顶部。

    回顾:转到页面 A,向下滚动并单击链接,将您带到页面 B。如果您使用浏览器的后退按钮导航回 A,则应该恢复滚动位置。如果您使用页面 B 上指向 A 的链接导航回 A,则滚动位置应位于页面顶部。

  2. 刷新页面时应恢复(保留)页面的滚动位置。刷新时页面会滚动到顶部。

我使用下面的代码解决了第一个问题,并接受第二个问题仍然是一个问题。我认为 Gatsby 和 Next.js 能够通过将滚动位置存储在会话存储中来解决刷新问题,这对于我的用例来说感觉有点大材小用。

import { MouseEventHandler, ReactNode } from "react";
import { Link } from "react-router-dom";
import { useNavigateToTop } from "./useNavigateToTop";

interface Props {
  children: ReactNode;
  className?: string;
  to: string;
}

/** Link to the top of a page so that the scroll position isn't persisted between pages. Use this instead of React's build-in @see {@link Link}. */
export const LinkToTop = (props: Props) => {
  const navigateToTop = useNavigateToTop();

  const navigateAndReset: MouseEventHandler<HTMLAnchorElement> = (event) => {
    event.preventDefault();
    navigateToTop(props.to);
  };

  return (
    <Link className={props.className} onClick={navigateAndReset} to={props.to}>
      {props.children}
    </Link>
  );
};

Run Code Online (Sandbox Code Playgroud)
import { useNavigate } from "react-router-dom";

/** Navigate to the top of a page so that the scroll position isn't persisted between pages. Use this instead of React Dom's build-in @see {@link useNavigate}. */
export const useNavigateToTop = () => {
  const navigate = useNavigate();

  const navigateAndReset = (to: string) => {
    navigate(to, { replace: true });
    window.scrollTo(0, 0);
  };

  return navigateAndReset;
};

Run Code Online (Sandbox Code Playgroud)