如何使用 React Router V5 默认滚动到顶部?

Posted

技术标签:

【中文标题】如何使用 React Router V5 默认滚动到顶部?【英文标题】:How to scroll to top by default using React Router V5? 【发布时间】:2021-08-12 18:59:25 【问题描述】:

作为标题,每次我使用<Redirect to="..."> 从一页重定向到另一页时,我都想滚动到顶部。本网站上现有的答案(尤其是这个热门话题:react-router scroll to top on every transition)都不起作用。

具体来说,我想要React 16.8+,功能组件,React Router V5-方法。我试过使用以下

const path = useRouteMatch()

实现下面的组件,作为整个<div className="App">的包装器,这样我就不用为每个想要这个效果的组件包装了:

ScrollToTop.js:

import useEffect from "react";
import useRouteMatch from "react-router-dom";

export const ScrollToTop = () => 
    const path = useRouteMatch()

    useEffect(() => 
        console.log("path =", path)
        window.scrollTo(0, 0)
    , [path])

    return null

我正在做的部分工作:

import React, useCallback, useEffect, useState from "react";
import ItemListPage from "../../commons/ItemListPage/ItemListPage";
import CreateButton from "../../commons/buttons/CreateButton";
import CreateProblemModal from "../Modal/CreateProblemModal";
import problemService from "../../../services/services";
import Spinner from "../../commons/Spinner";
import ProblemEditor from "../ProblemEditor";
import Link, Redirect, Route from "react-router-dom";
import TableCell from "../../../utils/TableCell";
import ProblemContext from "./ProblemContext";
import ScrollToTop from "../../commons/scrolling/ScrollToTop";

export const useProblemList = () => 
    const [problems, setProblems] = useState();
    const addProblem = (problem) => 
        problems.push(problem);
        setProblems(problems);
    ;

    return problems, setProblems, addProblem
;

const ProblemList = () => 
    const [showCreateProblemModal, setShowCreateProblemModal] = useState(false)
    const problems, setProblems = useProblemList();
    const [currentProblem, setCurrentProblem] = useState();
    const [shouldRedirect, setShouldRedirect] = useState(false)
    const refetchProblem = useCallback((problemId) => 
        problemService.getAllProblems()
            .then(problems => 
                setProblems(problems)
                setCurrentProblem(problems.find(problem => parseInt(problem.id) === parseInt(problemId)))
            )
    , [setProblems, setCurrentProblem])

    useEffect(() => 
        if (!problems || problems.length === 0) 
            refetchProblem()
        
    , [problems, refetchProblem]);

    const onProblemCreated = (problemId) => 
        refetchProblem(problemId)
        setShouldRedirect(true)
    

    if (!problems || (shouldRedirect && !currentProblem)) 
        return <Spinner/>
    

    return (
        <>
            <ScrollToTop/>
            shouldRedirect?
            <Redirect to=`problems/:problemId/edit`/> : ""
            <Route path="/problems" exact>
                <div className="problem-list font-poppins">
                    <div style=paddingTop: "20px", paddingBottom: "150px">
                        <div style=display: "flex", justifyContent: "center">
                            <ItemListPage title="Problem List"
                                          
                                          filterItems=["Filter", "Id", "tags"]
                                          Button=() =>
                                              <CreateButton onClick=() => setShowCreateProblemModal(true)/>
                                          tableHeaders=[
                                              <TableCell>#</TableCell>,
                                              <TableCell>Problem Title</TableCell>,
                                              <TableCell>Tags</TableCell>
                                          ]
                                          tableRowGenerator=
                                              list: problems,
                                              key: (problem) => problem.id,
                                              data: (problem) => [
                                                  <TableCell>
                                                      <Link to=`/problems/$problem.id/edit`
                                                            onClick=() => setCurrentProblem(problem)>
                                                          problem.id</Link>
                                                  </TableCell>,
                                                  <TableCell>
                                                      <Link to=`/problems/$problem.id/edit`
                                                            onClick=() => setCurrentProblem(problem)>
                                                          problem.title</Link>
                                                  </TableCell>,
                                                  <TableCell>
                                                      <span className="tag is-link">Functions</span>
                                                  </TableCell>,
                                              ]
                                          
                                          tableDataStyle=textAlign: "left"/>
                            <CreateProblemModal show=showCreateProblemModal
                                                onClose=() => setShowCreateProblemModal(false)
                                                onProblemCreated=onProblemCreated/>
                        </div>
                    </div>
                </div>
            </Route>
            <Route path="/problems/:problemId/edit">
                <ProblemContext.Provider value=
                    currentProblem, setCurrentProblem, refetchProblem, setShouldRedirect>
                    <ProblemEditor/>
                </ProblemContext.Provider>
            </Route>
        </>
    )



export ProblemList;

【问题讨论】:

你是说上面的代码 sn-p 不起作用?或者您希望它仅在组件首次安装时才滚动到顶部? @Tolumide:感谢您尝试帮助我。我的帖子中的一个只是其中之一作为示例。我想在每次 URL 更改时滚动到顶部,通过鼠标单击或 react-router-dom 提供的&lt;Redirect to="..."&gt; 例如:我有一个包含所有问题的 ProblemList,我必须滚动页面才能看到列表末尾附近的问题。当我单击其中一个的标题时,它将导航到我可以编辑问题的页面,而屏幕在中间 onEnter 我想要的是它从顶部开始。 问题已解决:删除overflow-y: auto 【参考方案1】:

考虑将您的 scrollToTop 实现更改为:

import * as React from "react";
import  useLocation  from "react-router";

export const ScrollToTop = () => 
    const  pathname  = useLocation();

    React.useEffect(() => 
        window.scrollTo(0, 0);
    , [pathname]);

    return null;
;

export const ScrollToTopOnMount = () => 
    React.useEffect(() => 
        window.scrollTo(0, 0);
    );
    return null;
;

注意useLocationuseRouteMatch 的具体用法

您可以在 React 路由器 docs 上阅读更多信息。

进一步说明

为了更好地解释这一点: 让我们考虑一个路由/names/:id,其中 id 是动态的。 如果您使用过:

const path = useRouteMatch(); path 只会检测到这个 url 的非动态部分,如果你去console.log(path),你会得到类似的东西: /names/:id

但是,如果您使用: const pathname = useLocation(); 您将获得包括动态 ID 在内的所有更改。所以,如果你去console.log(pathname),你会看到: /names/theDynamicId 其中id="theDynamicId"

文档对useLocation给出了清晰的描述

  The useLocation hook returns the location object that represents the 
  current URL. You can think about it like a useState that returns a new 
  location whenever the URL changes.

【讨论】:

根据我的测试,const path, url = useRouteMatch有两个输出pathurl,而path确实也输出了url的动态部分,例如http://localhost:3000/problems/3/edit。 (我认为将我们称为“URL”的东西命名为path,IMO 是他们的错)而pathname 工作的真正原因是path 仅输出组件&lt;ScrollToTop&gt; 开启的位置,而pathname 当我只将&lt;ScrollToTop&gt; 放在一个地方(即App.js)时,用户 的输出位置。【参考方案2】:

@Tolumide 的回答是正确的,并且如果您仍然有问题,请删除所有出现的

overflow-y: auto;

&lt;ScrollToTop&gt;组件中,关于为什么使用

const pathname = useLocation()

而不是

const path = useRouteMatch()

这是我通过尝试得出的结论:

useLocation()::pathname 报告用户在哪里,无论&lt;ScrollToTop&gt; 在哪里。 useRouteMatch()::path 报告组件 &lt;ScrollToTop&gt; 在哪里,无论用户在哪里。

【讨论】:

我已经更新了我的答案以包含解释

以上是关于如何使用 React Router V5 默认滚动到顶部?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 React Router v5 中推送到历史记录?

在使用 React Router v5 和 Redux 时,无法弄清楚如何将 props 传递给组件

react-router-dom V5 使用指南

react-router路由之routerRender方法(v5 v6)

如何在 React Router v5 中更新 URL 并替换历史状态而不重新渲染整个应用程序?

react-router v6对比react-router v5