Programowa nawigacja za pomocą react router V4

Właśnie zamieniłem react-router z v3 na v4.
Ale nie jestem pewien, jak programowo nawigować w funkcji członka Component. np. w funkcji handleClick() chcę przejść do /path/some/where po przetworzeniu niektórych danych. Kiedyś to robiłem przez:

import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')

Ale nie mogę znaleźć takich interfejsów w v4.
Jak mogę poruszać się za pomocą v4?

Author: Rubens Mariuzzo, 2017-02-08

17 answers

Jeśli kierujesz się do środowisk przeglądarek, musisz użyć pakietu react-router-dom zamiast react-router. Postępują zgodnie z tym samym podejściem co React, aby oddzielić Rdzeń (react) i kod specyficzny dla platformy, (react-dom, react-native ) z tą subtelną różnicą, że nie trzeba instalować dwóch oddzielnych pakietów, więc pakiety środowiskowe zawierają wszystko, czego potrzebujesz. Możesz dodać go do swojego projektu jako:

yarn add react-router-dom

Lub

npm i react-router-dom

The first thing you należy dostarczyć <BrowserRouter> jako najbardziej nadrzędny komponent w aplikacji. <BrowserRouter> używa API HTML5 history i zarządza nim za ciebie, więc nie musisz się martwić o utworzenie instancji i przekazanie jej do komponentu <BrowserRouter> jako propa(tak jak było to konieczne w poprzednich wersjach).

W V4, aby nawigować programowo, musisz uzyskać dostęp do obiektu history, który jest dostępny za pośrednictwem Reacta context, o ile masz <BrowserRouter> provider komponent jako najlepszy rodzic w Twojej aplikacji. Biblioteka wyświetla poprzez kontekst obiekt router, który sam zawiera history jako właściwość. Interfejs history oferuje kilka metod nawigacji, takich jak push, replace i goBack, m.in. Możesz sprawdzić całą listę właściwości i metod tutaj .

Ważna uwaga dla użytkowników Redux / Mobx

jeśli używasz redux lub mobx jako biblioteki zarządzania państwem w swojej aplikacji, być może przyszedłeś w przypadku problemów z komponentami, które powinny być zorientowane na lokalizację, ale nie są ponownie renderowane po uruchomieniu aktualizacji adresu URL

Dzieje się tak, ponieważ react-router przekazuje location komponentom używającym modelu kontekstowego.

Zarówno connect, jak i observer tworzą komponenty, których metody shouldComponentUpdate wykonują płytkie porównanie ich obecnych właściwości i następnych właściwości. Komponenty te będą ponownie renderowane tylko wtedy, gdy co najmniej jeden rekwizytor ulegnie zmianie. Oznacza to, że w celu zapewnienia aktualizują się, gdy zmienia się lokalizacja, będą musieli otrzymać właściwość, która zmienia się, gdy zmienia się lokalizacja.

Dwa podejścia do rozwiązania tego są:

  • zawiń swójpołączony komponent W bez ścieżki <Route />. Obecnie obiekt location jest jednym z właściwości, które <Route> przekazuje do komponentu, który renderuje
  • owiń swój połączony komponent składnikiem withRouter wyższego rzędu, który w rzeczywistości ma ten sam efekt i wstrzykuje location jako prop

Pomijając to, istnieją cztery sposoby nawigacji programowej, uporządkowane według zalecenia:

1.- Użycie komponentu <Route>

promuje styl deklaratywny. Przed v4 komponenty <Route /> były umieszczane na szczycie hierarchii komponentów, co wymagało wcześniejszego przemyślenia struktury tras. Jednak teraz możesz mieć <Route> komponenty w dowolnym miejscu w swoim drzewie, dzięki czemu masz lepszą kontrolę nad warunkowe renderowanie w zależności od adresu URL. Route match, location i history jako rekwizyty do twojego komponentu. Metody nawigacji (takie jak push, replace, goBack...) są dostępne jako właściwości obiektu history.

Istnieją 3 sposoby renderowania czegoś za pomocą Route, używając albo component, render lub children rekwizyty, ale nie używaj więcej niż jednego w tym samym Route. Wybór zależy od przypadku użycia, ale zasadniczo dwie pierwsze opcje będą renderować komponent tylko wtedy, gdy path dopasowuje adres url, podczas gdy z children komponent będzie renderowany niezależnie od tego, czy ścieżka pasuje do lokalizacji, czy nie (przydatne do dostosowania interfejsu użytkownika na podstawie dopasowania adresu URL).

Jeśli chcesz dostosować swoje wyjście renderowania komponentu , musisz zawinąć komponent w funkcję i użyć opcji render, aby przekazać komponentowi inne właściwości, oprócz match, location i history. Przykład do ilustracja:

import { BrowserRouter as Router } from 'react-router-dom'

const ButtonToNavigate = ({ title, history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    {title}
  </button>
);

const SomeComponent = () => (
  <Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)    

const App = () => (
  <Router>
    <SomeComponent /> // Notice how in v4 we can have any other component interleaved
    <AnotherComponent />
  </Router>
);

2.- Using withRouter HoC

Ten komponent wyższego rzędu wprowadzi te same właściwości co Route. Jednak niesie ze sobą ograniczenie, że można mieć tylko 1 HoC na plik.

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

const ButtonToNavigate = ({ history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    Navigate
  </button>
);


ButtonToNavigate.propTypes = {
  history: React.PropTypes.shape({
    push: React.PropTypes.func.isRequired,
  }),
};

export default withRouter(ButtonToNavigate);

3.- Użycie komponentu Redirect

renderowanie a {[56] } spowoduje przejście do nowej lokalizacji. Należy jednak pamiętać, że domyślnie bieżąca lokalizacja jest zastępowana nową, podobnie jak przekierowania po stronie serwera (HTTP 3XX). Na nowa lokalizacja jest dostarczana przez to prop, który może być ciągiem znaków (URL do przekierowania) lub obiektem location. Jeśli chcesz wcisnąć nowy wpis do historii, podaj propa push i ustaw go na true
<Redirect to="/your-new-location" push />

4.- Dostęp do router ręcznie przez context

trochę zniechęcony, ponieważ context jest wciąż eksperymentalnym API i prawdopodobnie złamie/zmieni się w przyszłych wydaniach Reacta
const ButtonToNavigate = (props, context) => (
  <button
    type="button"
    onClick={() => context.router.history.push('/my-new-location')}
  >
    Navigate to a new location
  </button>
);

ButtonToNavigate.contextTypes = {
  router: React.PropTypes.shape({
    history: React.PropTypes.object.isRequired,
  }),
};

Nie trzeba dodawać, że są też inne routery komponenty, które mają być przeznaczone dla ekosystemów innych niż przeglądarki, takie jak <NativeRouter>, które replikują stos nawigacji w pamięci i są skierowane na natywną platformę React, dostępną za pośrednictwem pakietu react-router-native.

Aby uzyskać więcej informacji, nie wahaj się rzucić okiem na oficjalne dokumenty . Istnieje również wideo wykonane przez jednego ze współautorów biblioteki, które stanowi całkiem fajne wprowadzenie do react-router v4, podkreślając niektóre z głównych zmian.

 426
Author: rgommezz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-12 13:56:14

Najprostszy sposób, aby to zrobić:

this.props.history.push("/new/url")

Uwaga:

  • możesz zdać history prop od komponentu nadrzędnego do komponentu, który ma zostać wywołany, jeśli nie jest on dostępny.
 158
Author: Jikku Jose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-09 05:28:22

Miałem podobny problem podczas migracji do React-Router v4, więc postaram się wyjaśnić moje rozwiązanie poniżej.

Proszę nie uważać tej odpowiedzi za właściwy sposób rozwiązania problemu, wyobrażam sobie, że istnieje duża szansa, że powstanie coś lepszego, gdy React Router v4 stanie się bardziej dojrzały i opuści wersję beta (może nawet już istnieje, a ja po prostu go nie odkryłem).

Dla kontekstu, miałem ten problem, ponieważ okazjonalnie używam Redux-Saga do programowej zmiany obiektu historii (powiedzmy, kiedy użytkownik pomyślnie uwierzytelni).

W dokumentacji Reactowego routera, spójrz na <Router> komponent i widać, że masz możliwość przekazania własnego obiektu historii za pomocą rekwizytu. To jest istota rozwiązania- dostarczamy obiekt historii do React-Router z global Moduł.

Kroki:

  1. zainstaluj moduł history npm - yarn add history lub npm install history --save
  2. Utwórz plik o nazwie history.js w Twój App.js poziom folderu (to było moje preferencje)

    // src/history.js
    
    import createHistory from 'history/createBrowserHistory';
    export default createHistory();`
    
  3. Dodaj ten obiekt historii do komponentu routera w ten sposób

    // src/App.js
    
    import history from '../your/path/to/history.js;'
    <Router history={history}>
    // Route tags here
    </Router>
    
  4. Dostosuj adres URL tak jak wcześniej, importując Twój obiekt historii globalnej:

    import history from '../your/path/to/history.js;'
    history.push('new/path/here/');
    

Wszystko powinno pozostać zsynchronizowane, a także masz dostęp do sposobu ustawiania obiektu historii programowo, a nie za pomocą komponentu / kontenera.

 56
Author: Alex Mann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-18 20:19:42

TL; DR:

if (navigate) {
  return <Redirect to="/" push={true} />
}

Prosta i deklaratywna odpowiedź jest taka, że należy użyć <Redirect to={URL} push={boolean} /> w połączeniu z setState()

Push: boolean - gdy true, przekierowanie spowoduje przeniesienie nowego wpisu do historii zamiast zastąpienia bieżącego.


import { Redirect } from 'react-router'

class FooBar extends React.Component {
  state = {
    navigate: false
  }

  render() {
    const { navigate } = this.state

    // here is the important part
    if (navigate) {
      return <Redirect to="/" push={true} />
    }
   // ^^^^^^^^^^^^^^^^^^^^^^^

    return (
      <div>
        <button onClick={() => this.setState({ navigate: true })}>
          Home
        </button>
      </div>
    )
  }
}

Pełny przykład tutaj . Czytaj więcej tutaj .

PS. Przykład używa ES7+ Property Initializers do inicjalizacji stanu. Zobacz tutaj jak również, jeśli jesteś zainteresowany.

 47
Author: Lyubomir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-15 12:43:39

Użyj useHistory Hooka, jeśli używasz komponentów funkcji

Możesz użyć useHistory hook to get history instance.

import { useHistory } from "react-router-dom";

const MyComponent = () => {
  const history = useHistory();
  
  return (
    <button onClick={() => history.push("/about")}>
      Click me
    </button>
  );
}

Hook useHistory daje Ci dostęp do instancji historii, której możesz użyć do nawigacji.

Użyj właściwości history wewnątrz komponentów strony

React Router wprowadza pewne właściwości, w tym history do komponentów strony.

class HomePage extends React.Component {
  render() {
    const { history } = this.props;

    return (
      <div>
        <button onClick={() => history.push("/projects")}>
          Projects
        </button>
      </div>
    );
  }
}

Zawijanie elementów potomnych withRouter aby wprowadzić właściwości routera

withRouter wrapper wstrzykuje router właściwości komponentów. Na przykład możesz użyć tego opakowania, aby wstrzyknąć router do komponentu przycisku wylogowania umieszczonego w menu użytkownika.

import { withRouter } from "react-router";

const LogoutButton = withRouter(({ history }) => {
  return (
    <button onClick={() => history.push("/login")}>
      Logout
    </button>
  );
});

export default LogoutButton;
 29
Author: TheMisir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-21 19:24:48

Możesz również użyć właściwości, aby uzyskać dostęp do obiektu historii: this.props.history.push('new_url')

 11
Author: user1445685,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-02 05:36:20

Krok 1: jest tylko jedna rzecz do zaimportowania na górze:

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

Krok 2: na swojej trasie podaj historię:

<Route
  exact
  path='/posts/add'
  render={({history}) => (
    <PostAdd history={history} />
  )}
/>

Krok 3: historia zostaje zaakceptowana jako część właściwości w następnym komponencie, więc możesz po prostu:

this.props.history.push('/');
To było łatwe i naprawdę potężne.
 9
Author: cdi-meo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-06 20:44:06

To działa:

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

const SomeComponent = withRouter(({ history }) => (
    <div onClick={() => history.push('/path/some/where')}>
        some clickable element
    </div>); 
);

export default SomeComponent;
 7
Author: Kaya Toast,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-20 11:34:16

Moja odpowiedź jest podobna do Alexa . Nie jestem pewien, dlaczego React-Router tak niepotrzebnie to skomplikował. Dlaczego powinienem owijać mój komponent HoC, aby uzyskać dostęp do tego, co zasadniczo jest globalne?

W każdym razie, jeśli przyjrzysz się, jak zaimplementowali <BrowserRouter>, to tylko małe opakowanie wokół historii .

Możemy wyciągnąć tę historię tak, że możemy importować ją z dowolnego miejsca. Sztuczka polega jednak na tym, że jeśli robisz renderowanie po stronie serwera i spróbuj import moduł historii, to nie będzie działać, ponieważ używa tylko przeglądarki API. Ale to jest w porządku, ponieważ zwykle przekierowujemy tylko w odpowiedzi na kliknięcie lub inne zdarzenie po stronie klienta. Więc chyba można to podrobić:

// history.js
if(__SERVER__) {
    module.exports = {};
} else {
    module.exports = require('history').createBrowserHistory();
}

Z pomocą webpacka możemy zdefiniować niektóre var-y, aby wiedzieć, w jakim środowisku się znajdujemy:

plugins: [
    new DefinePlugin({
        '__SERVER__': 'false',
        '__BROWSER__': 'true', // you really only need one of these, but I like to have both
    }),

A teraz możesz

import history from './history';

Z dowolnego miejsca. Zwróci tylko pusty moduł na serwerze.

Jeśli nie chcesz użyć tej magii vars, musisz tylko require w obiekcie globalnym, gdzie jest to potrzebne (wewnątrz twojego Handlera zdarzeń). import nie zadziała, ponieważ działa tylko na najwyższym poziomie.

 5
Author: mpen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-09 17:20:28

Myślę, że @rgommezz obejmuje większość przypadków minus jeden, który moim zdaniem jest dość ważny.

// history is already a dependency or React Router, but if don't have it then try npm install save-dev history

import createHistory from "history/createBrowserHistory"

// in your function then call add the below 
const history = createHistory();
// Use push, replace, and go to navigate around.
history.push("/home");

To pozwala mi napisać prosty serwis z akcjami/wywołaniami, które mogę wywołać, aby zrobić nawigację z dowolnego komponentu, który chcę, bez robienia wielu rzeczy na moich komponentach...

Nie jest jasne, dlaczego nikt wcześniej nie dostarczył takiego rozwiązania. Mam nadzieję, że to pomoże, i jeśli widzisz jakiś problem z nim proszę dać mi znać.

 5
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-23 04:54:10

Od kilku dni testuję v4 i. Podoba mi się jak na razie! Po jakimś czasie to ma sens.

Miałem również to samo pytanie i stwierdziłem, że obsługa tego jak poniżej działa najlepiej (i może nawet być jak to jest zamierzone). Używa state, operatora trójdzielnego i <Redirect>.

W konstruktorze ()

this.state = {
    redirectTo: null
} 
this.clickhandler = this.clickhandler.bind(this);

W render ()

render(){
    return (
        <div>
        { this.state.redirectTo ?
            <Redirect to={{ pathname: this.state.redirectTo }} /> : 
            (
             <div>
               ..
             <button onClick={ this.clickhandler } />
              ..
             </div>
             )
         }

In the clickhandler ()

 this.setState({ redirectTo: '/path/some/where' });
Mam nadzieję, że to pomoże. Daj mi znać.
 4
Author: jar0m1r,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-01 21:10:01

Zmagałem się z tym przez jakiś czas-coś tak prostego, a jednocześnie tak skomplikowanego, bo ReactJS to po prostu zupełnie inny sposób pisania aplikacji internetowych, jest bardzo obcy dla nas starszych ludzi!

Stworzyłem oddzielny komponent do abstrakcji bałaganu:

// LinkButton.js

import React from "react";
import PropTypes from "prop-types";
import {Route} from 'react-router-dom';

export default class LinkButton extends React.Component {

    render() {
        return (
            <Route render={({history}) => (
                <button {...this.props}
                       onClick={() => {
                           history.push(this.props.to)
                       }}>
                    {this.props.children}
                </button>
            )}/>
        );
    }
}

LinkButton.propTypes = {
    to: PropTypes.string.isRequired
};

Następnie dodaj go do swojej metody render():

<LinkButton className="btn btn-primary" to="/location">
    Button Text
</LinkButton>
 4
Author: mwieczorek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-21 14:52:17

Ponieważ nie ma innego sposobu, aby poradzić sobie z tym okropnym projektem, napisałem ogólny komponent, który używa withRouter hoc podejście. Poniższy przykład to zawijanie elementu button, ale możesz zmienić go na dowolny klikalny element, którego potrzebujesz:

import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';

const NavButton = (props) => (
  <Button onClick={() => props.history.push(props.to)}>
    {props.children}
  </Button>
);

NavButton.propTypes = {
  history: PropTypes.shape({
    push: PropTypes.func.isRequired
  }),
  to: PropTypes.string.isRequired
};

export default withRouter(NavButton);

Użycie:

<NavButton to="/somewhere">Click me</NavButton>
 3
Author: rodrigocfd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-26 02:10:34
this.props.history.push("/url")

Jeśli tego nie znalazłeś.rekwizyty.historia dostępna w Twoim komponencie , następnie spróbuj tego

import {withRouter} from 'react-router-dom'
export default withRouter(MyComponent)  
 3
Author: Shashwat Gupta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-29 11:39:16

Możesz nawigować warunkowo w ten sposób

import { useHistory } from "react-router-dom";

function HomeButton() {
  const history = useHistory();

  function handleClick() {
    history.push("/path/some/where");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}
 3
Author: Rijo Rajan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-19 09:35:04

Ponieważ czasami wolę przełączać trasy przez aplikację, a następnie przez przyciski, jest to minimalny działający przykład, który działa dla mnie:

import { Component } from 'react'
import { BrowserRouter as Router, Link } from 'react-router-dom'

class App extends Component {
  constructor(props) {
    super(props)

    /** @type BrowserRouter */
    this.router = undefined
  }

  async handleSignFormSubmit() {
    await magic()
    this.router.history.push('/')
  }

  render() {
    return (
      <Router ref={ el => this.router = el }>
        <Link to="/signin">Sign in</Link>
        <Route path="/signin" exact={true} render={() => (
          <SignPage onFormSubmit={ this.handleSignFormSubmit } />
        )} />
      </Router>
    )
  }
}
 2
Author: piotr_cz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-13 21:17:54

Dla tych z Was, którzy wymagają przekierowania przed całkowitym initalizacją routera za pomocą React Router lub React Router Dom, możesz podać przekierowanie, po prostu przystępując do obiektu historii i naciskając na niego nowy stan w swoim constructur z app.js. Rozważmy następujące:

function getSubdomain(hostname) {
    let regexParse = new RegExp('[a-z\-0-9]{2,63}\.[a-z\.]{2,5}$');
    let urlParts = regexParse.exec(hostname);
    return hostname.replace(urlParts[0], '').slice(0, -1);
}

class App extends Component {

    constructor(props) {
        super(props);


        this.state = {
            hostState: true
        };

        if (getSubdomain(window.location.hostname).length > 0) {
            this.state.hostState = false;
            window.history.pushState('', '', './login');
        } else {
            console.log(getSubdomain(window.location.hostname));
        }

    }


    render() {
        return (

            <BrowserRouter>
                {this.state.hostState ? (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                        <Route path="/" component={PublicContainer}/>
                    </div>
                ) : (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                    </div>
                )

                }
            </BrowserRouter>)
    }


}

Tutaj chcemy zmienić trasy wyjściowe zależne od subdomeny, poprzez interakcję z obiektem history przed renderowaniem komponentu możemy skutecznie przekierować, pozostawiając nasze trasy w takcie.

window.history.pushState('', '', './login');
 0
Author: li x,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-11 20:11:10