Как прослушивать изменения маршрута в React Router v4?

139

У меня есть пара кнопок, которые действуют как маршруты. Каждый раз при изменении маршрута я хочу убедиться, что активная кнопка меняется.

Есть ли способ прослушать изменения маршрута в реакции маршрутизатора v4?

Kasper
источник

Ответы:

170

Я использую , withRouterчтобы получить locationопору. Когда компонент обновляется из-за нового маршрута, я проверяю, изменилось ли значение:

@withRouter
class App extends React.Component {

  static propTypes = {
    location: React.PropTypes.object.isRequired
  }

  // ...

  componentDidUpdate(prevProps) {
    if (this.props.location !== prevProps.location) {
      this.onRouteChanged();
    }
  }

  onRouteChanged() {
    console.log("ROUTE CHANGED");
  }

  // ...
  render(){
    return <Switch>
        <Route path="/" exact component={HomePage} />
        <Route path="/checkout" component={CheckoutPage} />
        <Route path="/success" component={SuccessPage} />
        // ...
        <Route component={NotFound} />
      </Switch>
  }
}

Надеюсь, поможет

brickpop
источник
21
Используйте this.props.location.pathname в реактивном маршрутизаторе v4.
ptorsson
4
@ledfusion Я делаю то же самое и использую withRouter, но получаю ошибку You should not use <Route> or withRouter() outside a <Router>. Я не вижу ни одного <Router/>компонента в приведенном выше коде. Так как это работает?
индивидуалист
1
Привет @maverick. Я не уверен, как выглядит ваш код, но в приведенном выше примере <Switch>компонент действует как де-факто маршрутизатор. <Route>Будет отображена только первая запись с совпадающим путем. В <Router/>этом сценарии нет необходимости в каких-либо компонентах
Brickpop
1
для использования @withRouter вам необходимо установить npm install --save-dev transform-decorators-legacy
Sigex
68

Чтобы расширить вышесказанное, вам нужно будет добраться до объекта истории. Если вы используете BrowserRouter, вы можете импортировать withRouterи обернуть свой компонент компонентом более высокого порядка (HoC) , чтобы иметь доступ через реквизиты к свойствам и функциям объекта истории.

import { withRouter } from 'react-router-dom';

const myComponent = ({ history }) => {

    history.listen((location, action) => {
        // location is an object like window.location
        console.log(action, location.pathname, location.state)
    });

    return <div>...</div>;
};

export default withRouter(myComponent);

Единственное, о чем следует помнить, это то, что withRouter и большинство других способов доступа к history к объекту, похоже, загрязняют свойства, поскольку они объект в нем.

Сэм Парментер
источник
Ответ помог мне кое-что понять независимо от вопроса :). Но исправить withRoutesв withRouter.
Сергей Реуцкий
Да, извините, спасибо, что указали на это. Я исправил пост. Я поставил правильный импорт в начале вопроса, а затем неправильно написал его в примере кода.
Сэм Парментер,
5
Я думаю, что текущая версия withRouter передает, historyа не переменную listen.
mikebridge
5
Было бы хорошо изменить пост, чтобы продемонстрировать, что он не слушает; в этом коде есть утечка памяти.
AndrewSouthpaw
34

Вам следует использовать history v4 lib.

Пример оттуда

history.listen((location, action) => {
  console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
  console.log(`The last navigation action was ${action}`)
})
Сергей Реуцкий
источник
1
Вызовы history.pushState () и history.replaceState () не запускают событие popstate, поэтому одно это не покрывает все изменения маршрута.
Райан
1
@Ryan Похоже, history.pushэто срабатывает history.listen. См. Пример использования базового URL-адреса в документации по истории v4 . Поскольку на historyсамом деле это оболочка собственного historyобъекта браузера, она не ведет себя точно так же, как собственный объект.
Rockallite
Это кажется лучшим решением, поскольку часто вам нужно прослушивать изменения маршрута для отправки событий, которые не связаны с реакцией на события жизненного цикла компонентов.
Daniel Dubovski
12
Возможная утечка памяти! Очень важно, чтобы вы это сделали! «Когда вы присоединяете слушателя с помощью history.listen, он возвращает функцию, которую можно использовать для удаления слушателя, которая затем может быть вызвана в логике очистки:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos
Перейдите сюда для документации по пакету истории. github.com/ReactTraining/history/blob/master/docs/…
Джейсон Ким
26

withRouter, history.listenИ useEffect(React Ловушки) работает довольно хорошо вместе:

import React, { useEffect } from 'react'
import { withRouter } from 'react-router-dom'

const Component = ({ history }) => {
    useEffect(() => history.listen(() => {
        // do something on route change
        // for my example, close a drawer
    }), [])

    //...
}

export default withRouter(Component)

Обратный вызов слушателя будет срабатывать каждый раз при изменении маршрута, а возврат для history.listen- это обработчик завершения работы, с которым хорошо работает useEffect.

noetix
источник
13

v5.1 представляет полезный хук useLocation

https://reacttraining.com/blog/react-router-v5-1/#uselocation

import { Switch, useLocation } from 'react-router-dom'

function usePageViews() {
  let location = useLocation()

  useEffect(
    () => {
      ga.send(['pageview', location.pathname])
    },
    [location]
  )
}

function App() {
  usePageViews()
  return <Switch>{/* your routes here */}</Switch>
}
Mugen
источник
4
Просто к сведению , как у меня была проблема с ошибкой: Cannot read property 'location' of undefined at useLocation. Вы должны убедиться, что вызов useLocation () находится не в том же компоненте, который помещает маршрутизатор в дерево: см. Здесь
toddg
11

С крючками:

import { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { history as historyShape } from 'react-router-prop-types'

const DebugHistory = ({ history }) => {
  useEffect(() => {
    console.log('> Router', history.action, history.location])
  }, [history.location.key])

  return null
}

DebugHistory.propTypes = { history: historyShape }

export default withRouter(DebugHistory)

Импорт и рендеринг как <DebugHistory>компонент

Tymek
источник
11
import React, { useEffect } from 'react';
import { useLocation } from 'react-router';

function MyApp() {

  const location = useLocation();

  useEffect(() => {
      console.log('route has been changed');
      ...your code
  },[location.pathname]);

}

с крючками

Натрий Юнайтед
источник
Холли Джесис! как это работает? Ваш ответ классный! бути поставить точку отладчика в useEffect, но каждый раз, когда я меняю путь, местоположение остается неопределенным ? можешь поделиться какой-нибудь хорошей статьей? потому что трудно найти какую-либо четкую информацию
AlexNikonov
7
import { useHistory } from 'react-router-dom';

const Scroll = () => {
  const history = useHistory();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [history.location.pathname]);

  return null;
}
Вейя Оу
источник
Он также отслеживает изменения хеша? route / a # 1 -> route / a # 2
Нарен
1

С помощью react Hooks я использую useEffect

  const history = useHistory()
  const queryString = require('query-string')
  const parsed = queryString.parse(location.search)
  const [search, setSearch] = useState(parsed.search ? parsed.search : '')

  useEffect(() => {
    const parsedSearch = parsed.search ? parsed.search : ''
    if (parsedSearch !== search) {
      // do some action! The route Changed!
    }
  }, [location.search])
Алан
источник
0

В некоторых случаях вы можете использовать renderатрибут вместо component, таким образом:

class App extends React.Component {

    constructor (props) {
        super(props);
    }

    onRouteChange (pageId) {
        console.log(pageId);
    }

    render () {
        return  <Switch>
                    <Route path="/" exact render={(props) => { 
                        this.onRouteChange('home');
                        return <HomePage {...props} />;
                    }} />
                    <Route path="/checkout" exact render={(props) => { 
                        this.onRouteChange('checkout');
                        return <CheckoutPage {...props} />;
                    }} />
                </Switch>
    }
}

Обратите внимание: если вы измените состояние в onRouteChangeметоде, это может вызвать ошибку «Превышена максимальная глубина обновления».

Фернандо
источник
0

С помощью useEffectловушки можно обнаруживать изменения маршрута без добавления слушателя.

import React, { useEffect }           from 'react';
import { Switch, Route, withRouter }  from 'react-router-dom';
import Main                           from './Main';
import Blog                           from './Blog';


const App  = ({history}) => {

    useEffect( () => {

        // When route changes, history.location.pathname changes as well
        // And the code will execute after this line

    }, [history.location.pathname]);

    return (<Switch>
              <Route exact path = '/'     component = {Main}/>
              <Route exact path = '/blog' component = {Blog}/>
            </Switch>);

}

export default withRouter(App);
Эрик Мартин Джордан
источник
0

Я только что разобрался с этой проблемой, поэтому добавлю свое решение в качестве дополнения к другим предоставленным ответам.

Проблема в том, что на useEffectсамом деле это не работает так, как вы хотели бы, так как вызов запускается только после первого рендера, поэтому возникает нежелательная задержка.
Если вы используете какой-нибудь менеджер состояний, такой как, например, redux, есть вероятность, что вы увидите мерцание на экране из-за длительного состояния в магазине.

Что вы действительно хотите, так это использовать, useLayoutEffectпоскольку это срабатывает немедленно.

Поэтому я написал небольшую служебную функцию, которую поместил в тот же каталог, что и мой маршрутизатор:

export const callApis = (fn, path) => {
    useLayoutEffect(() => {
      fn();
    }, [path]);
};

Который я называю из компонента HOC следующим образом:

callApis(() => getTopicById({topicId}), path);

pathэто опора, которая передается в matchобъекте при использовании withRouter.

Я не совсем сторонник того, чтобы вручную слушать / отменять историю. Это просто имо.

след
источник