【问题标题】:In react router v4 how does one link to a fragment identifier?在反应路由器 v4 中,如何链接到片段标识符?
【发布时间】:2017-03-06 04:21:31
【问题描述】:

鉴于页面非常简单(假设 React 和 react-router@4 已导入):

// Current location: example.com/about
<Link to="/about/#the-team">See the team</Link>
// ... loads of content ... //
<a id="the-team"></a>

我希望上述内容在单击“查看团队”后会向下滚动到已识别的团队锚点。 url 正确更新为:example.com/about#the-team,但不会向下滚动。

我尝试过诸如&lt;a name="the-team"&gt;&lt;/a&gt; 之类的替代方法,但我相信这不再是规范(也不起作用)。

github 上有很多针对 react-router@v2 的解决方法,但它们依赖于 BrowserRouter 上存在的更新回调,而 v4 中不再存在。

【问题讨论】:

  • 你试过这个吗? github.com/rafrex/react-router-hash-link-scroll 这取决于浏览器历史记录,如果它解决了您的问题,我会更新我的答案
  • 看起来它依赖于 react router @v2 ?
  • 试试看是否可行,可能只依赖于浏览器历史,重定向到下面的hash
  • 问题在于 React Router v4 没有 onUpdate 回调
  • 哦,好吧,我会尝试找出其他东西

标签: javascript reactjs react-router


【解决方案1】:

给定一个&lt;ScrollIntoView&gt; 组件,它接受要滚动到的元素的 id:

class ScrollIntoView extends React.Component {

  componentDidMount() {
    this.scroll()
  }

  componentDidUpdate() {
    this.scroll()
  }

  scroll() {
    const { id } = this.props
    if (!id) {
      return
    }
    const element = document.querySelector(id)
    if (element) {
      element.scrollIntoView()
    }
  }

  render() {
    return this.props.children
  }
}

您可以将视图组件的内容包装在其中:

const About = (props) => (
  <ScrollIntoView id={props.location.hash}>
    // ...
  </ScrollIntoView>
)

或者你可以创建一个匹配包装器:

const MatchWithHash = ({ component:Component, ...props }) => (
  <Match {...props} render={(props) => (
    <ScrollIntoView id={props.location.hash}>
      <Component {...props} />
    </ScrollIntoView>
  )} />
)

用法是:

<MatchWithHash pattern='/about' component={About} />

一个完全充实的解决方案可能需要考虑边缘情况,但我对上述情况进行了快速测试,它似乎有效。

编辑:

这个组件现在可以通过 npm 获得。 GitHub:https://github.com/pshrmn/rrc

npm install --save rrc

import { ScrollIntoView } from 'rrc'

【讨论】:

  • 谢谢,感谢您的回答,但边缘情况确实是我正在寻找的。上面的操作与我认为下面的答案没有什么不同
  • 我在&lt;ScrollTo&gt; 组件中添加了一个componentDidUpdate 函数,它允许它处理滚动到加载页面中的哈希ID 元素。这也不应该破坏&lt;Miss&gt;,因为它没有注册错误匹配(另一个答案给出的通配符&lt;Match&gt;)。
  • 啊,我明白了。你需要用这个 HOC 包装每条路线,对吗?另外,我不相信可以依赖componentShouldUpdate,因为如果单击相同的链接两次(因为不会发生变化),它不会触发 - 例如,单击“#About us”会向下滚动到关于我们部分,手动向上滚动,再次单击“#关于我们”,没有任何反应。虽然我认为这种方法正在让我们关闭。
  • 点击&lt;Link&gt; 到同一位置时实际上发生了变化。这是因为浏览器历史记录会为推送到它的每个位置创建一个新密钥。
  • 这很有趣。这是作为上下文传递下来的吗?
【解决方案2】:

react-router 团队似乎正在积极跟踪这个问题(在撰写本文时,v4 还没有完全发布)。

作为临时解决方案,以下工作正常。

EDIT 3 现在可以放心地忽略这个答案,并接受正确的答案。左,因为它处理的问题略有不同。

EDIT2 以下方法会导致其他问题,包括但不限于单击Section A,然后再次单击Section A 不起作用。似乎也不适用于任何类型的动画(感觉动画开始,但被后来的状态更改覆盖)

EDIT 请注意,以下内容确实搞砸了 Miss 组件。仍在寻找更强大的解决方案

// App
<Router>
    <div>
        <Match pattern="*" component={HashWatcher} />

        <ul>
            <li><Link to="/#section-a">Section A</Link></li>
            <li><Link to="/#section-b">Section B</Link></li>
        </ul>


        <Match pattern="/" component={Home} />

    </div>
</Router>


// Home 
// Stock standard mark up
<div id="section-a">
    Section A content
</div>
<div id="section-b">
    Section B content
</div>

然后,HashWatcher 组件将如下所示。它是“监听”所有路由更改的临时组件

import { Component } from 'react';

export default class HashWatcher extends Component {

    componentDidMount() {
        if(this.props.location.hash !== "") {
            this.scrollToId(this.hashToId(this.props.location.hash));
        }
    }

    componentDidUpdate(prevProps) {
        // Reset the position to the top on each location change. This can be followed up by the
        // following hash check.
        // Note, react-router correctly sets the hash and path, even if using HashHistory
        if(prevProps.location.pathname !== this.props.location.pathname) {
            this.scrollToTop();
        }

        // Initially checked if hash changed, but wasn't enough, if the user clicked the same hash 
        // twice - for example, clicking contact us, scroll to top, then contact us again
        if(this.props.location.hash !== "") {
            this.scrollToId(this.hashToId(this.props.location.hash));
        }
    }

    /**
     * Remove the leading # on the hash value
     * @param  string hash
     * @return string
     */
    hashToId(hash) {
        return hash.substring(1);
    }

    /**
     * Scroll back to the top of the given window
     * @return undefined
     */
    scrollToTop() {
        window.scrollTo(0, 0);
    }

    /**
     * Scroll to a given id on the page
     * @param  string id The id to scroll to
     * @return undefined
     */
    scrollToId(id) {
        document.getElementById(id).scrollIntoView();
    }

    /**
     * Intentionally return null, as we never want this component actually visible.
     * @return {[type]} [description]
     */
    render() {
        return null;
    }
}

【讨论】:

【解决方案3】:

我创建了一个名为 react-scroll-manager 的库,它解决了这个问题以及与 React Router 滚动位置相关的其他问题。它使用this technique 导航到文档中任何位置的哈希链接,而无需单独包装它们。只需将 Router 组件包装在 ScrollManager 组件中即可:

class App extends React.Component {
  constructor() {
    super();
    this.history = createHistory();
  }
  render() {
    return (
      <ScrollManager history={this.history}>
        <Router history={this.history}>
          ...
        </Router>
      </ScrollManager>
    );
  }
}

您可以使用id 属性链接到任何组件:

<MyComponent id="mycomp">...</MyComponent>

只需将id 作为片段包含在您的链接目标中:

<Link to="#mycomp">...</Link>

该库基于 HTML5 和 React 16,它支持 React Router 4(可能还有更早的版本)。

【讨论】:

    猜你喜欢
    • 2018-02-16
    • 2018-01-04
    • 1970-01-01
    • 2017-12-15
    • 2019-02-04
    • 2023-03-08
    • 2017-06-14
    • 2017-10-21
    • 2018-09-22
    相关资源
    最近更新 更多