Jak słuchać zmian tras w React Router v4?


Odpowiedzi:


179

Używam withRouterdo zdobycia locationrekwizytu. Kiedy komponent jest aktualizowany z powodu nowej trasy, sprawdzam, czy zmieniła się wartość:

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

Mam nadzieję, że to pomoże


22
Użyj „this.props.location.pathname” w React router v4.
ptorsson,

4
@ledfusion Robię to samo i używam withRouter, ale pojawia się błąd You should not use <Route> or withRouter() outside a <Router>. Nie widzę żadnego <Router/>komponentu w powyższym kodzie. Jak to działa?
darKnight

1
Cześć @maverick. Nie jestem pewien, jak wygląda Twój kod, ale w powyższym przykładzie <Switch>komponent działa jako de facto router. <Route>Wyrenderowany zostanie tylko pierwszy wpis, który ma pasującą ścieżkę. W <Router/>tym scenariuszu nie jest potrzebny żaden komponent
Brickpop

1
aby korzystać z @withRouter, musisz zainstalować npm install --save-dev transform-decorators-legacy
Sigex

jak dokładnie jest z routerem.
Jovylle Bermudez

73

Aby rozwinąć powyższe, musisz dostać się do obiektu historii. Jeśli używasz BrowserRouter, możesz zaimportować withRouteri opakować swój komponent komponentem wyższego rzędu (HoC) , aby mieć dostęp poprzez właściwości do właściwości i funkcji obiektu historii.

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

Jedyną rzeczą, o której należy pamiętać, jest to, że przy użyciu routera i większości innych sposobów uzyskiwania dostępu do obiektów historyzdaje się, że zanieczyszczają one właściwości, gdy przekształcają one obiekt w jego strukturę.


1
Odpowiedź pomogła mi coś zrozumieć bez względu na pytanie :). Ale naprawić withRoutessię withRouter.
Sergey Reutskiy

1
Tak, przepraszam, dziękuję za wskazanie tego. Poprawiłem post. Umieściłem poprawny import na górze pytania, a następnie błędnie napisałem go w przykładzie kodu.
Sam Parmenter,

5
Myślę, że obecna wersja withRouter przechodzi, historya nie zmienna listen.
mikebridge

5
Dobrze byłoby zmienić post, aby pokazać, że nie słucha; ten kod zawiera wyciek pamięci.
AndrewSouthpaw

powinieneś zapisać się tylko raz! Teraz zasubskrybujesz ponownie renderowanie każdego komponentu.
Ievgen Naida

35

Powinieneś użyć historii v4 lib.

Przykład z tego miejsca

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
Wywołania history.pushState () i history.replaceState () nie wyzwalają zdarzenia popstate, więc samo to nie obejmie wszystkich zmian tras.
Ryan,

1
@Ryan Wygląda na to, że history.pushwyzwala history.listen. Zobacz przykład użycia podstawowego adresu URL w dokumentach historii v4 . Ponieważ historyjest to w rzeczywistości opakowanie natywnego historyobiektu przeglądarki, nie zachowuje się dokładnie tak, jak natywny.
Rockallite

Wydaje się, że jest to lepsze rozwiązanie, ponieważ często trzeba słuchać zmian tras w celu wypychania zdarzeń, co nie jest związane z reagowaniem na zdarzenia cyklu życia komponentów.
Daniel Dubovski,

12
Potencjalny wyciek pamięci! Bardzo ważne, żebyś to zrobił! „Kiedy dołączasz odbiornik przy użyciu history.listen, zwraca on funkcję, której można użyć do usunięcia nasłuchiwania, którą można następnie wywołać w logice czyszczenia:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos

Przejdź tutaj, aby uzyskać dokumentację dotyczącą pakietu historii. github.com/ReactTraining/history/blob/master/docs/…
Jason Kim,

27

withRouter, history.listeni useEffect(React Hooks) całkiem dobrze razem współpracują:

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)

Wywołanie zwrotne listenera zostanie uruchomione za każdym razem, gdy zostanie zmieniona trasa, a zwrotem dla history.listenjest program obsługi zamykania, z którym dobrze się bawi useEffect.


27

Wersja 5.1 wprowadza przydatny hak 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>
}

10
Tylko uwaga, jak miałem problemy z błędem: Cannot read property 'location' of undefined at useLocation. Musisz się upewnić, że wywołanie useLocation () nie znajduje się w tym samym komponencie, który umieszcza router w drzewie: patrz tutaj
toddg

to niestety nie uruchamia akcji na zagnieżdżonych trasach
Eugene Kuzmenko

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

}

z haczykami


Holly Jesys! jak to działa? Twoja odpowiedź jest fajna! ale wprowadziłem punkt debuggera w useEffect, ale za każdym razem, gdy zmieniłem ścieżkę, lokalizacja pozostawała niezdefiniowana ? czy możesz udostępnić jakiś dobry artykuł? bo ciężko jest znaleźć jakieś jasne informacje
AlexNikonov

Użyj react-router-domzamiastreact-router
Nolesh

12

Z haczykami:

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)

Importuj i renderuj jako <DebugHistory>komponent


7
import { useHistory } from 'react-router-dom';

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

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

  return null;
}

Czy obserwuje również zmiany hash? route / a # 1 -> route / a # 2
Naren

2

Korzystam z 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])

2

W przypadku komponentów funkcjonalnych spróbuj useEffect z props.location.

import React, {useEffect} from 'react';

const SampleComponent = (props) => {

      useEffect(() => {
        console.log(props.location);
      }, [props.location]);

}

export default SampleComponent;

1
dzięki za to. Myślałem, że muszę spędzić godziny, aby debugować mój kod, ale tego właśnie potrzebowałem haha
giantqtipz

1

W niektórych przypadkach możesz użyć renderatrybutu zamiast component, w ten sposób:

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

Zwróć uwagę, że jeśli zmienisz stan onRouteChangemetody, może to spowodować błąd „Przekroczono maksymalną głębokość aktualizacji”.


0

Dzięki useEffecthakowi można wykryć zmiany trasy bez dodawania nasłuchiwania.

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

Właśnie poradziłem sobie z tym problemem, więc dodam moje rozwiązanie jako uzupełnienie innych udzielonych odpowiedzi.

Problem polega na tym, useEffectże nie działa tak, jak byś tego chciał, ponieważ wywołanie jest wyzwalane dopiero po pierwszym renderowaniu, więc występuje niepożądane opóźnienie.
Jeśli używasz jakiegoś menedżera stanu, takiego jak redux, istnieje prawdopodobieństwo, że pojawi się migotanie na ekranie z powodu utrzymującego się stanu w sklepie.

To, czego naprawdę chcesz, to użyć useLayoutEffect ponieważ jest to uruchamiane natychmiast.

Napisałem więc małą funkcję narzędzia, którą umieściłem w tym samym katalogu co mój router:

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

Które dzwonię z poziomu HOC komponentu w ten sposób:

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

pathto właściwość, która jest przekazywana w matchobiekcie podczas używania withRouter.

Nie jestem zwolennikiem ręcznego odsłuchiwania / nieposłuchiwania historii. To tylko imo.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.