【问题标题】:Use anchors with react-router使用带有 react-router 的锚点
【发布时间】:2017-03-09 21:18:10
【问题描述】:

如何使用 react-router,并让链接导航到特定页面上的特定位置? (例如/home-page#section-three

详情

我在我的 React 应用程序中使用 react-router

我有一个站点范围的导航栏,需要链接到页面的特定部分,例如 /home-page#section-three

因此,即使您说/blog,单击此链接仍会加载主页,第三部分会滚动到视图中。这正是标准<a href="/home-page#section-three> 的工作方式。

注意:react-router 的创建者并没有给出明确的答案。他们说它正在进行中,同时使用其他人的答案。我会尽我最大的努力保持这个问题的最新进展和可能的解决方案,直到出现一个占主导地位的问题。

研究


How to use normal anchor links with react-router

这个问题来自 2015 年(所以 10 年前的反应时间)。最受欢迎的答案是使用HistoryLocation 而不是HashLocation。基本上,这意味着将位置存储在窗口历史记录中,而不是存储在哈希片段中。

坏消息是……即使使用 HistoryLocation(大多数教程和文档在 2016 年都这么说),锚标签仍然不起作用。


https://github.com/ReactTraining/react-router/issues/394

关于如何在 react-router 中使用锚链接的 ReactTraining 线程。这不是确定的答案。请小心,因为大多数建议的答案都已过时(例如,在 <Link> 中使用“hash”道具)


【问题讨论】:

    标签: javascript reactjs routes react-router anchor


    【解决方案1】:

    React Router Hash Link 为我工作,易于安装和实施:

    $ npm install --save react-router-hash-link
    

    在您的 component.js 中将其作为链接导入:

    import { HashLink as Link } from 'react-router-hash-link';
    

    不要使用锚<a>,而是使用<Link>

    <Link to="home-page#section-three">Section three</Link>
    

    注意:我用@987654322@代替@987654323@

    【讨论】:

    • 这是我的首选解决方案
    • 对我来说很好。网址看起来像 localhost:8080/#!/#services
    • 它也有使用 typescript 的类型:npm install @types/react-router-hash-link
    • 此解决方案无法以编程方式推送历史记录。
    • 这终于对我有用了。它不起作用,因为我将 ID 插入到组件中,而不是 DOM 元素中。
    【解决方案2】:

    这是我找到的一种解决方案(2016 年 10 月)。它是跨浏览器兼容的(在 Internet Explorer、Firefox、Chrome、移动 Safari 和Safari 中测试)。

    您可以为路由器提供onUpdate 属性。每当路线更新时,都会调用此方法。此方案使用 onUpdate 属性检查是否存在与哈希匹配的 DOM 元素,然后在路由转换完成后滚动到该元素。

    您必须使用 browserHistory 而不是 hashHistory。

    答案是 Hash links #394 中的“Rafrax”。

    将此代码添加到您定义&lt;Router&gt;的位置:

    import React from 'react';
    import { render } from 'react-dom';
    import { Router, Route, browserHistory } from 'react-router';
    
    const routes = (
      // your routes
    );
    
    function hashLinkScroll() {
      const { hash } = window.location;
      if (hash !== '') {
        // Push onto callback queue so it runs after the DOM is updated,
        // this is required when navigating from a different page so that
        // the element is rendered on the page before trying to getElementById.
        setTimeout(() => {
          const id = hash.replace('#', '');
          const element = document.getElementById(id);
          if (element) element.scrollIntoView();
        }, 0);
      }
    }
    
    render(
      <Router
        history={browserHistory}
        routes={routes}
        onUpdate={hashLinkScroll}
      />,
      document.getElementById('root')
    )
    

    如果您感到懒惰并且不想复制该代码,则可以使用仅为您定义该功能的 Anchorate。 https://github.com/adjohnson916/anchorate

    【讨论】:

    • 只想提一下,这个解决方案将不再适用,因为react-router 的第 4 版,onUpdate 方法已被删除。
    • 刚刚发布了react-router V4的解决方案,见下文。
    【解决方案3】:

    此解决方案适用于 react-router v5

    import React, { useEffect } from 'react'
    import { Route, Switch, useLocation } from 'react-router-dom'
    
    export default function App() {
      const { pathname, hash, key } = useLocation();
    
      useEffect(() => {
        // if not a hash link, scroll to top
        if (hash === '') {
          window.scrollTo(0, 0);
        }
        // else scroll to id
        else {
          setTimeout(() => {
            const id = hash.replace('#', '');
            const element = document.getElementById(id);
            if (element) {
              element.scrollIntoView();
            }
          }, 0);
        }
      }, [pathname, hash, key]); // do this on route change
    
      return (
          <Switch>
            <Route exact path="/" component={Home} />
            .
            .
          </Switch>
      )
    }
    

    在组件中

    <Link to="/#home"> Home </Link>
    

    【讨论】:

    • 这很好用。我希望这个解决方案更加突出!
    • tnx @JimmyTheCode
    • 不错的答案。 react-router-hash-link 对我来说效果不佳。我进行了编辑以改进答案:(1)hash 缺少作为useEffect 的依赖项(2)如果我们依赖location.key,我们可以保证它仍然会滚动到&lt;Link /&gt; 上的目标点击。用例:假设用户点击&lt;Link /&gt;然后滚动离开,如果我们不依赖key,再次点击同样的&lt;Link /&gt;不会有任何效果。
    • 啊,0ms 的超时时间对于本地路由更改效果很好,但是从另一个页面来看,它没有足够的时间来渲染目标元素。
    • 我可以确认这适用于react-router v6。 react-router-hash-link 对我不起作用。
    【解决方案4】:

    这是一个简单的解决方案,不需要任何订阅或第三方软件包。它应该适用于react-router@3 及以上和react-router-dom

    工作示例https://fglet.codesandbox.io/

    来源(不幸的是,它目前在编辑器中不起作用):


    #ScrollHandler 钩子示例

    import { useEffect } from "react";
    import PropTypes from "prop-types";
    import { withRouter } from "react-router-dom";
    
    const ScrollHandler = ({ location, children }) => {
      useEffect(
        () => {
          const element = document.getElementById(location.hash.replace("#", ""));
    
          setTimeout(() => {
            window.scrollTo({
              behavior: element ? "smooth" : "auto",
              top: element ? element.offsetTop : 0
            });
          }, 100);
        }, [location]);
      );
    
      return children;
    };
    
    ScrollHandler.propTypes = {
      children: PropTypes.node.isRequired,
      location: PropTypes.shape({
        hash: PropTypes.string,
      }).isRequired
    };
    
    export default withRouter(ScrollHandler);
    

    #ScrollHandler 类示例

    import { PureComponent } from "react";
    import PropTypes from "prop-types";
    import { withRouter } from "react-router-dom";
    
    class ScrollHandler extends PureComponent {
      componentDidMount = () => this.handleScroll();
    
      componentDidUpdate = prevProps => {
        const { location: { pathname, hash } } = this.props;
        if (
          pathname !== prevProps.location.pathname ||
          hash !== prevProps.location.hash
        ) {
          this.handleScroll();
        }
      };
    
      handleScroll = () => {
        const { location: { hash } } = this.props;
        const element = document.getElementById(hash.replace("#", ""));
    
        setTimeout(() => {
          window.scrollTo({
            behavior: element ? "smooth" : "auto",
            top: element ? element.offsetTop : 0
          });
        }, 100);
      };
    
      render = () => this.props.children;
    };
    
    ScrollHandler.propTypes = {
      children: PropTypes.node.isRequired,
      location: PropTypes.shape({
        hash: PropTypes.string,
        pathname: PropTypes.string,
      })
    };
    
    export default withRouter(ScrollHandler);
    

    【讨论】:

    • 甜蜜,谢谢。为什么是 element.offsetTop 而不是 window.scrollY + element.getBoundingClientRect().top ?后者使其独立于最近的亲属。
    • 在这个简单的例子中,计算element.offsetTop 将得到与window.scrollY + element.getBoundingClientRect().top 相同的结果。但是,如果您将元素嵌套在 table 中,那么是的,您需要使用后者而不是前者。例如嵌套tablejsfiddle.net/pLuvbyx5,未嵌套元素:jsfiddle.net/8bwj6yz3
    • 有什么办法可以避免 setTimeOut 吗?我们可以在不使用 setTimeOut 的情况下实现相同的功能吗? stackoverflow.com/questions/64224547/…
    • 不幸的是,没有。某些浏览器(如 Safari)不会立即更新滚动位置。
    • @MattCarlotta 假设我的页面渲染时间超过 100 毫秒,在这种情况下它可以工作吗?如果是的话,请告诉我们一些关于它的信息。你能解决这个stackoverflow.com/questions/64224547/…
    【解决方案5】:

    只是避免使用 react-router 进行本地滚动:

    document.getElementById('myElementSomewhere').scrollIntoView() 
    

    【讨论】:

    • 理想情况下,本地滚动会通过路由器,因为这样您就可以从外部链接到文档的特定部分,但是这个答案仍然非常感谢,因为它告诉了我需要在我的this.props.history.listen 回调。
    • 在我的情况下,我只是想通过模仿与 href 为 #myElementId 的链接相同的方式向下滚动到一个 div ......这确实是最好和简单的答案,谢谢!
    【解决方案6】:

    Don P's answer 的问题是,如果该部分依赖于某些异步操作,则有时仍会呈现或加载具有 id 的元素。以下函数将尝试通过 id 查找元素并导航到该元素并每 100 毫秒重试一次,直到达到最多 50 次重试:

    scrollToLocation = () => {
      const { hash } = window.location;
      if (hash !== '') {
        let retries = 0;
        const id = hash.replace('#', '');
        const scroll = () => {
          retries += 0;
          if (retries > 50) return;
          const element = document.getElementById(id);
          if (element) {
            setTimeout(() => element.scrollIntoView(), 0);
          } else {
            setTimeout(scroll, 100);
          }
        };
        scroll();
      }
    }
    

    【讨论】:

    • 上限为 5 秒。如果页面在 5 秒后加载,它会起作用吗?
    • 在这个问题的所有 SO 解决方案中,这必须是迄今为止最简单的。与&lt;Link to={{ pathname: "/", hash: "elementIDtoScrollTo"}}&gt; 一起使用
    • 使用后最好使用if (hash.length &gt; 0)而不是if (hash == "")
    【解决方案7】:

    我将 Don P's solution(见上文)改编为 react-router 4(2019 年 1 月),因为 &lt;Router&gt; 上不再有 onUpdate 属性。

    import React from 'react';
    import * as ReactDOM from 'react-dom';
    import { Router, Route } from 'react-router';
    import { createBrowserHistory } from 'history';
    
    const browserHistory = createBrowserHistory();
    
    browserHistory.listen(location => {
        const { hash } = location;
        if (hash !== '') {
            // Push onto callback queue so it runs after the DOM is updated,
            // this is required when navigating from a different page so that
            // the element is rendered on the page before trying to getElementById.
            setTimeout(
                () => {
                    const id = hash.replace('#', '');
                    const element = document.getElementById(id);
                    if (element) {
                        element.scrollIntoView();
                    }
                },
                0
            );
        }
    });
    
    ReactDOM.render(
      <Router history={browserHistory}>
          // insert your routes here...
      />,
      document.getElementById('root')
    )
    

    【讨论】:

    • 这仍然是最新的吗?我不存在 history 属性。
    【解决方案8】:
    <Link to='/homepage#faq-1'>Question 1</Link>
    
    useEffect(() => {
        const hash = props.history.location.hash
        if (hash && document.getElementById(hash.substr(1))) {
            // Check if there is a hash and if an element with that id exists
            document.getElementById(hash.substr(1)).scrollIntoView({behavior: "smooth"})
        }
    }, [props.history.location.hash]) // Fires when component mounts and every time hash changes
    

    【讨论】:

      【解决方案9】:

      对于简单的页面内导航,您可以添加类似这样的内容,尽管它不处理初始化页面 -

      // handle back/fwd buttons
      function hashHandler() {
        const id = window.location.hash.slice(1) // remove leading '#'
        const el = document.getElementById(id)
        if (el) {
          el.scrollIntoView()
        }
      }
      window.addEventListener('hashchange', hashHandler, false)
      

      【讨论】:

      • 当我在 API 调用后调用它以获取页面内容时,该代码实际上对我在 React 应用程序中进行初始页面加载有效。我喜欢它的简单性,并且相同的页面链接已经对我有用。
      【解决方案10】:

      另一种选择:react-scrollchor https://www.npmjs.com/package/react-scrollchor

      react-scrollchor:一个 React 组件,用于滚动到具有流畅动画的 #hash 链接。 Scrollchor 是 Scroll 和 Anchor 的混合体

      注意:它不使用 react-router

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-04-21
        • 1970-01-01
        • 2018-06-21
        • 1970-01-01
        • 2018-10-09
        • 2018-01-20
        • 2015-10-23
        • 2021-08-03
        相关资源
        最近更新 更多