В ответном маршрутизаторе v4 как ссылка на идентификатор фрагмента?

Учитывая очень простую страницу (предположив, что React и response-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. URL правильно обновляется до: example.com/about#the-team, но он не прокручивается вниз.

Я попробовал альтернативы, такие как <a name="the-team"></a>, но я считаю, что это больше не спецификация (и она не работает).

В github есть много проблем для response-router @v2, но они полагаются на обратный вызов обновления, присутствующий на BrowserRouter, который больше не присутствует в v4.

Ответ 1

<ScrollIntoView> компонент <ScrollIntoView> который принимает идентификатор элемента для прокрутки до:

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'

Ответ 2

Команда реактивного маршрутизатора, похоже, активно отслеживает эту проблему (на момент написания 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;
    }
}

Ответ 3

Я создал библиотеку под названиемact -scroll-manager, которая решает эту проблему и другие проблемы, связанные с положением прокрутки в React Router. Он использует эту технику для навигации по хеш-ссылкам в любом месте документа без необходимости оборачивать их по отдельности. Просто оберните ваш компонент 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 (и, возможно, более ранние версии).