Як слухати зміни маршруту в реакційному маршрутизаторі v4?


138

У мене є пара кнопок, яка виконує роль маршрутів. Кожного разу, коли маршрут змінюється, я хочу переконатися, що кнопка, яка є активною, змінюється.

Чи є спосіб прослухати зміни маршруту в реакторі маршрутизатора 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/>компонента у наведеному вище коді. То як це працює?
maverick

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);

Єдине, про що слід пам’ятати, це те, що за допомогою рутера та більшості інших способів доступу, 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 () не запускають попідставні події, тому одне не покриє всіх змін маршруту.
Райан

1
@Ryan Схоже, history.pushце і спрацьовує history.listen. Див. Приклад Використання базової URL-адреси в документах історії v4 . Оскільки historyце насправді обгортка рідного historyоб’єкта браузера, він не веде себе так, як рідний.
Rockallite

Це здається кращим рішенням, оскільки часто потрібно слухати зміни маршруту для поштовху подій, що не пов'язане з реагуванням на події життєвого циклу компонентів.
Даніель Дубовський

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, але будь-коли я змінив ім'я шляху, де розташування залишилося невизначеним ? Ви можете поділитися будь-якою гарною статтею?
Оскільки

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
Naren

1

Я реагую на Гачки 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.