Zagnieżdżone trasy z reakcyjnym routerem v4 / v5

261

Obecnie mam problemy z zagnieżdżaniem tras przy użyciu routera reagującego v4.

Najbliższym przykładem była konfiguracja trasy w dokumentacji React-Router v4 .

Chcę podzielić moją aplikację na 2 różne części.

Frontend i obszar administracyjny.

Myślałem o czymś takim:

<Match pattern="/" component={Frontpage}>
  <Match pattern="/home" component={HomePage} />
  <Match pattern="/about" component={AboutPage} />
</Match>
<Match pattern="/admin" component={Backend}>
  <Match pattern="/home" component={Dashboard} />
  <Match pattern="/users" component={UserPage} />
</Match>
<Miss component={NotFoundPage} />

Frontend ma inny układ i styl niż obszar administracyjny. Tak więc na pierwszej stronie trasy do domu, a więc i tak powinny być trasy podrzędne.

/ home powinien być renderowany w komponencie Frontpage, a / admin / home powinien być renderowany w komponencie Backend.

Próbowałem pewnych wariantów, ale zawsze kończyłem się nie trafieniem / home lub / admin / home.

Edycja - 19.04.2017

Ponieważ ten post ma teraz wiele wyświetleń, zaktualizowałem go o ostateczne rozwiązanie. Mam nadzieję, że to komuś pomoże.

Edycja - 08.05.2017

Usunięto stare rozwiązania

Ostateczne rozwiązanie:

To jest ostateczne rozwiązanie, którego używam teraz. Ten przykład zawiera również globalny komponent błędu, taki jak tradycyjna strona 404.

import React, { Component } from 'react';
import { Switch, Route, Redirect, Link } from 'react-router-dom';

const Home = () => <div><h1>Home</h1></div>;
const User = () => <div><h1>User</h1></div>;
const Error = () => <div><h1>Error</h1></div>

const Frontend = props => {
  console.log('Frontend');
  return (
    <div>
      <h2>Frontend</h2>
      <p><Link to="/">Root</Link></p>
      <p><Link to="/user">User</Link></p>
      <p><Link to="/admin">Backend</Link></p>
      <p><Link to="/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
      <Switch>
        <Route exact path='/' component={Home}/>
        <Route path='/user' component={User}/>
        <Redirect to={{
          state: { error: true }
        }} />
      </Switch>
      <footer>Bottom</footer>
    </div>
  );
}

const Backend = props => {
  console.log('Backend');
  return (
    <div>
      <h2>Backend</h2>
      <p><Link to="/admin">Root</Link></p>
      <p><Link to="/admin/user">User</Link></p>
      <p><Link to="/">Frontend</Link></p>
      <p><Link to="/admin/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
      <Switch>
        <Route exact path='/admin' component={Home}/>
        <Route path='/admin/user' component={User}/>
        <Redirect to={{
          state: { error: true }
        }} />
      </Switch>
      <footer>Bottom</footer>
    </div>
  );
}

class GlobalErrorSwitch extends Component {
  previousLocation = this.props.location

  componentWillUpdate(nextProps) {
    const { location } = this.props;

    if (nextProps.history.action !== 'POP'
      && (!location.state || !location.state.error)) {
        this.previousLocation = this.props.location
    };
  }

  render() {
    const { location } = this.props;
    const isError = !!(
      location.state &&
      location.state.error &&
      this.previousLocation !== location // not initial render
    )

    return (
      <div>
        {          
          isError
          ? <Route component={Error} />
          : <Switch location={isError ? this.previousLocation : location}>
              <Route path="/admin" component={Backend} />
              <Route path="/" component={Frontend} />
            </Switch>}
      </div>
    )
  }
}

class App extends Component {
  render() {
    return <Route component={GlobalErrorSwitch} />
  }
}

export default App;
datoml
źródło
1
Dziękujemy za zaktualizowanie pytania z ostateczną odpowiedzią! tylko sugestia: może mógłbyś zachować tylko czwarty wpis i pierwszy, ponieważ inne używają przestarzałych wersji interfejsu API i odwracają uwagę od odpowiedzi
Giuliano Vilela
lol, nie mam pojęcia, jaki jest format tej daty: 08.05.2017 Sugeruję użycie uniwersalnego formatu ISO8601 dla dat, jeśli nie chcesz mylić ludzi. jest 08 miesiącem lub dniem? ISO8601 = rok.miesiąc.dzień godzina. Minuta.sekunda (stopniowo bardziej szczegółowe)
wesm
Ładne zaktualizowane rozwiązanie końcowe, ale myślę, że nie potrzebujesz previousLocationlogiki.
tudorpavel
jaka jest motywacja do całkowitego przepisania routera reagującego? Lepiej, żeby to był dobry powód
Oliver Watkins,
To podejście deklaratywne. Możesz więc skonfigurować swoje trasy tak, jakbyś używał składników reagujących.
data

Odpowiedzi:

318

W React-Router-v4 nie zagnieżdżasz się <Routes />. Zamiast tego umieścisz je w innym <Component />.


Na przykład

<Route path='/topics' component={Topics}>
  <Route path='/topics/:topicId' component={Topic} />
</Route>

powinno stać się

<Route path='/topics' component={Topics} />

z

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <Link to={`${match.url}/exampleTopicId`}>
      Example topic
    </Link>
    <Route path={`${match.path}/:topicId`} component={Topic}/>
  </div>
) 

Oto podstawowy przykład prosto z dokumentacji routera reagującego .

Lyubomir
źródło
Jestem w stanie zaimplementować z twojego linku w prostym przykładzie, jednak kiedy ręcznie wpisuję adres URL, nie działa on na moim serwerze localhost. ale robi to na twoim przykładzie. Z drugiej strony HashRouter działa poprawnie, gdy ręcznie wpisuję adres URL za pomocą #. Czy masz pojęcie, dlaczego na moim serwerze hosta lokalnego BrowserRouter nie działa, gdy ręcznie wpisuję adres URL?
himawan_r 18.04.17
8
czy możesz uczynić komponent Tematy częścią klasy? i gdzie jest parametr „dopasuj”? w render ()?
user1076813,
21
Wydaje się śmieszne nie można po prostu to="exampleTopicId"z ${match.url}bycia niejawny.
greenimpala
8
Możesz mieć zagnieżdżone trasy według dokumentów reagtraining.com/react-router/web/example/route-config . Umożliwiłoby to scentralizowaną konfigurację trasy zgodnie z tematem w dokumentacji. Pomyśl, jak szaleństwem byłoby zarządzanie w większym projekcie, jeśli jest ono niedostępne.
JustDave,
5
Nie są to trasy zagnieżdżone, jest to routing jednopoziomowy, który nadal korzysta z właściwości renderowania trasy, która pobiera składnik funkcjonalny jako dane wejściowe, spójrz uważniej, nie ma zagnieżdżania w sensie reagowania routera <4. RouteWithSubRoutes to jeden -poziomowa lista tras wykorzystujących dopasowanie wzorca.
Lyubomir,
102

react-router v6

Aktualizacja do 2020 roku: nadchodzące wydanie v6 będzie zawierać zagnieżdżone Routekomponenty, które Just Work ™. Zobacz przykładowy kod w tym poście na blogu .

Chociaż pierwotne pytanie dotyczy wersji v4 / v5 , właściwa odpowiedź, gdy statki w wersji v6 będą po prostu jej używać, jeśli możesz . Obecnie jest w wersji alfa.


react-router v4 & v5

Prawdą jest, że aby zagnieździć Trasy, musisz umieścić je w podrzędnym komponencie Trasy.

Jeśli jednak wolisz składnię bardziej wbudowaną niż rozbijanie tras między komponenty, możesz dostarczyć funkcjonalny komponent do renderrekwizytu trasy, pod którą chcesz zagnieździć.

<BrowserRouter>

  <Route path="/" component={Frontpage} exact />
  <Route path="/home" component={HomePage} />
  <Route path="/about" component={AboutPage} />

  <Route
    path="/admin"
    render={({ match: { url } }) => (
      <>
        <Route path={`${url}/`} component={Backend} exact />
        <Route path={`${url}/home`} component={Dashboard} />
        <Route path={`${url}/users`} component={UserPage} />
      </>
    )}
  />

</BrowserRouter>

Jeśli interesuje Cię, dlaczego renderrekwizyt powinien być użyty, a nie componentrekwizyt, dzieje się tak dlatego, że zapobiega on ponownemu zamontowaniu wbudowanego komponentu funkcjonalnego na każdym renderowaniu. Więcej informacji znajduje się w dokumentacji .

Zauważ, że przykład otacza zagnieżdżone trasy we fragmencie . Przed React 16 możesz <div>zamiast tego użyć kontenera .

Davnicwil
źródło
16
Dzięki Bogu jedyne rozwiązanie, które jest dość jasne, możliwe do utrzymania i działa zgodnie z oczekiwaniami. Chciałbym zareagować router 3 zagnieżdżone trasy wróciły.
Merunas Grincalaitis
ten idealny wygląda jak kątowy wylot trasy
Partha Ranjan
4
Powinieneś użyć match.path, nie match.url. Ten pierwszy jest zwykle używany w pathrekwizytach trasy ; ten ostatni, kiedy przepychasz nową trasę (np. Link toprop)
nbkhope
50

Chciałem tylko wspomnieć, że router- reakcyjny v4 zmienił się radykalnie od czasu wysłania / odpowiedzi na to pytanie.

Nie ma <Match> komponentu! <Switch>jest upewnienie się, że renderowane jest tylko pierwsze dopasowanie. <Redirect>no .. przekierowuje na inną trasę. Użyj lub pomiń, exactaby włączyć lub wyłączyć dopasowanie częściowe.

Zobacz dokumenty. Oni są świetni. https://reacttraining.com/react-router/

Oto przykład, mam nadzieję, że będzie użyteczny, aby odpowiedzieć na twoje pytanie.

<Router>
  <div>
    <Redirect exact from='/' to='/front'/>
    <Route path="/" render={() => {
      return (
        <div>
          <h2>Home menu</h2>
          <Link to="/front">front</Link>
          <Link to="/back">back</Link>
        </div>
      );
    }} />          
    <Route path="/front" render={() => {
      return (
        <div>
        <h2>front menu</h2>
        <Link to="/front/help">help</Link>
        <Link to="/front/about">about</Link>
        </div>
      );
    }} />
    <Route exact path="/front/help" render={() => {
      return <h2>front help</h2>;
    }} />
    <Route exact path="/front/about" render={() => {
      return <h2>front about</h2>;
    }} />
    <Route path="/back" render={() => {
      return (
        <div>
        <h2>back menu</h2>
        <Link to="/back/help">help</Link>
        <Link to="/back/about">about</Link>
        </div>
      );
    }} />
    <Route exact path="/back/help" render={() => {
      return <h2>back help</h2>;
    }} />
    <Route exact path="/back/about" render={() => {
      return <h2>back about</h2>;
    }} />
  </div>
</Router>

Mam nadzieję, że to pomogło, daj mi znać. Jeśli ten przykład nie odpowiada wystarczająco dobrze na twoje pytanie, powiedz mi, a zobaczę, czy mogę je zmodyfikować.

jar0m1r
źródło
Nie ma żadnych exactna Redirect reagtraining.com/react-router/web/api/Redirect To byłoby o wiele czystsze niż <Route exact path="/" render={() => <Redirect to="/path" />} />to, co robię zamiast tego. Przynajmniej nie pozwoli mi to na TypeScript.
Filuren
2
Czy rozumiem poprawnie, że nie ma już takich tras zagnieżdżonych / podrzędnych? czy muszę powielić trasę podstawową na wszystkich trasach? Czy router-reakcyjny 4 nie zapewnia żadnej pomocy w konstruowaniu tras w sposób możliwy do utrzymania?
Ville,
5
@Ville Jestem zdumiony; znalazłeś lepsze rozwiązanie? Nie chcę mieć tras
dookoła, cholera
1
to zadziała, ale upewnij się, że ustawiłeś ścieżkę publiczną na „/” w konfiguracji webpack dla pliku bundle.js, w przeciwnym razie zagnieżdżone trasy nie będą działać przy odświeżaniu strony.
vikrant 10.10.17
6

Coś takiego.

import React from 'react';
import {
  BrowserRouter as Router, Route, NavLink, Switch, Link
} from 'react-router-dom';

import '../assets/styles/App.css';

const Home = () =>
  <NormalNavLinks>
    <h1>HOME</h1>
  </NormalNavLinks>;
const About = () =>
  <NormalNavLinks>
    <h1>About</h1>
  </NormalNavLinks>;
const Help = () =>
  <NormalNavLinks>
    <h1>Help</h1>
  </NormalNavLinks>;

const AdminHome = () =>
  <AdminNavLinks>
    <h1>root</h1>
  </AdminNavLinks>;

const AdminAbout = () =>
  <AdminNavLinks>
    <h1>Admin about</h1>
  </AdminNavLinks>;

const AdminHelp = () =>
  <AdminNavLinks>
    <h1>Admin Help</h1>
  </AdminNavLinks>;


const AdminNavLinks = (props) => (
  <div>
    <h2>Admin Menu</h2>
    <NavLink exact to="/admin">Admin Home</NavLink>
    <NavLink to="/admin/help">Admin Help</NavLink>
    <NavLink to="/admin/about">Admin About</NavLink>
    <Link to="/">Home</Link>
    {props.children}
  </div>
);

const NormalNavLinks = (props) => (
  <div>
    <h2>Normal Menu</h2>
    <NavLink exact to="/">Home</NavLink>
    <NavLink to="/help">Help</NavLink>
    <NavLink to="/about">About</NavLink>
    <Link to="/admin">Admin</Link>
    {props.children}
  </div>
);

const App = () => (
  <Router>
    <div>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/help" component={Help}/>
        <Route path="/about" component={About}/>

        <Route exact path="/admin" component={AdminHome}/>
        <Route path="/admin/help" component={AdminHelp}/>
        <Route path="/admin/about" component={AdminAbout}/>
      </Switch>

    </div>
  </Router>
);


export default App;

Sanjeev Shakya
źródło
6

Udało mi się zdefiniować zagnieżdżone trasy przez zawinięcie Switchi zdefiniowanie zagnieżdżonej trasy przed trasą główną.

<BrowserRouter>
  <Switch>
    <Route path="/staffs/:id/edit" component={StaffEdit} />
    <Route path="/staffs/:id" component={StaffShow} />
    <Route path="/staffs" component={StaffIndex} />
  </Switch>
</BrowserRouter>

Odniesienie: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md

asukiaaa
źródło
zmiana kolejności rozwiązała mój problem, chociaż nie wiem, czy będzie to miało jakieś skutki uboczne. ale na razie pracuję .. dzięki :)
Anbu369,
2

Możesz spróbować czegoś takiego jak Routes.js

import React, { Component } from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom';
import FrontPage from './FrontPage';
import Dashboard from './Dashboard';
import AboutPage from './AboutPage';
import Backend from './Backend';
import Homepage from './Homepage';
import UserPage from './UserPage';
class Routes extends Component {
    render() {
        return (
            <div>
                <Route exact path="/" component={FrontPage} />
                <Route exact path="/home" component={Homepage} />
                <Route exact path="/about" component={AboutPage} />
                <Route exact path="/admin" component={Backend} />
                <Route exact path="/admin/home" component={Dashboard} />
                <Route exact path="/users" component={UserPage} />    
            </div>
        )
    }
}

export default Routes

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route } from 'react-router-dom'
import Routes from './Routes';

class App extends Component {
  render() {
    return (
      <div className="App">
      <Router>
        <Routes/>
      </Router>
      </div>
    );
  }
}

export default App;

Myślę, że możesz to samo osiągnąć również tutaj.

Aniruddh Agarwal
źródło
Słuszna uwaga! Tak samo myślałem, kiedy zaczynałem od React po rozwoju aplikacji rozruchowej Java Spring. jedyne, co chciałbym zmienić, to „div” na „Switch” w Routes.js. I tbh, możesz zdefiniować wszystkie trasy w App.js, ale owinąć je na zewnątrz w pliku index.js na przykład (create-reag-app)
Reborn
Tak masz rację! Wdrożyłem w ten sposób, dlatego wspominam tę metodę.
Aniruddh Agarwal
-6
interface IDefaultLayoutProps {
    children: React.ReactNode
}

const DefaultLayout: React.SFC<IDefaultLayoutProps> = ({children}) => {
    return (
        <div className="DefaultLayout">
            {children}
        </div>
    );
}


const LayoutRoute: React.SFC<IDefaultLayoutRouteProps & RouteProps> = ({component: Component, layout: Layout, ...rest}) => {
const handleRender = (matchProps: RouteComponentProps<{}, StaticContext>) => (
        <Layout>
            <Component {...matchProps} />
        </Layout>
    );

    return (
        <Route {...rest} render={handleRender}/>
    );
}

const ScreenRouter = () => (
    <BrowserRouter>
        <div>
            <Link to="/">Home</Link>
            <Link to="/counter">Counter</Link>
            <Switch>
                <LayoutRoute path="/" exact={true} layout={DefaultLayout} component={HomeScreen} />
                <LayoutRoute path="/counter" layout={DashboardLayout} component={CounterScreen} />
            </Switch>
        </div>
    </BrowserRouter>
);
EVGENY GLUKHOV
źródło