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


139

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

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


Ответы:


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>
  }
}

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


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.


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>
}

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>компонент


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.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.