【问题标题】:How to scroll to top by default using React Router V5?如何使用 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"
                                          width="1000px"
                                          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

标签: reactjs react-router react-router-dom


【解决方案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; 在哪里,无论用户在哪里。

【讨论】:

  • 我已经更新了我的答案以包含解释
猜你喜欢
  • 1970-01-01
  • 2022-01-09
  • 2021-01-29
  • 2021-09-22
  • 2014-12-14
  • 2018-12-03
  • 2018-04-28
  • 2018-10-30
相关资源
最近更新 更多