问题 在react router v4中,如何链接到片段标识符?


鉴于非常简单的页面(假设已经导入了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>

我希望上面的内容,点击“查看团队”后会向下滚动到id'ed团队主播。网址正确更新为: example.com/about#the-team,但它不向下滚动。

我尝试了其他替代品 <a name="the-team"></a> 但我相信这不再是规范(也不适用)。

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


7679
2017-10-24 11:46


起源

你试试这个吗? github.com/rafrex/react-router-hash-link-scroll 这取决于浏览器的历史,如果它解决了你的问题,我会更新我的答案 - Pritish Vaidya
看起来它依赖于反应路由器@ v2? - Chris
尝试它是否有效,可能因为它只依赖于browserHistory,重定向到以下哈希 - Pritish Vaidya
问题是React Router v4没有onUpdate回调 - Chris
哦,好吧,我会试着找出别的东西 - Pritish Vaidya


答案:


鉴于一个 <ScrollIntoView> 获取要滚动到的元素的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获得

npm install --save rrc

import { ScrollIntoView } from 'rrc'

8
2017-11-26 06:24



谢谢,欣赏答案,但边缘情况真的是我在寻找。我认为以上操作与我的答案没有什么不同 - Chris
我加了一个 componentDidUpdate 功能 <ScrollTo> 允许它处理滚动到加载页面中的哈希id元素的组件。这也不应该破裂 <Miss> 因为它没有注册错误匹配(通配符 <Match> 另一个答案给出了)。 - Paul S
啊,我明白了。你需要用这个HOC包裹每条路线吗?另外,我不相信 componentShouldUpdate 可以依赖,因为如果同一个链接被点击两次就不会触发(因为没有变化) - 例如,点击“#About us”向下滚动到关于我们的部分,手动向上滚动,点击“#关于我们“再次,没有任何反应。虽然我认为这种方法让我们闭嘴。 - Chris
单击a时实际上有变化 <Link> 到同一个地方。这是因为浏览器历史记录会为推送到它的每个位置创建一个新密钥。 - Paul S
那很有意思。这是通过上下文传递的吗? - Chris


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

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

编辑3 现在可以通过已接受的答案安全地忽略此答案。因为它解决问题略有不同。

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

编辑 请注意以下操作会搞乱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;
    }
}

1
2017-11-08 15:10



你有一些关于这方面的信息吗? stackoverflow.com/questions/36681051/... - WitVault