Verschachtelte Routen mit React Router v4 / v5

261

Ich habe derzeit Probleme mit der Verschachtelung von Routen mit React Router v4.

Das nächste Beispiel war die Routenkonfiguration in der React-Router v4-Dokumentation .

Ich möchte meine App in 2 verschiedene Teile teilen.

Ein Frontend und ein Admin-Bereich.

Ich habe über so etwas nachgedacht:

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

Das Frontend hat ein anderes Layout und einen anderen Stil als der Admin-Bereich. Also innerhalb der Titelseite die Route nach Hause, ungefähr und so sollte man die untergeordneten Routen sein.

/ home sollte in die Frontpage-Komponente und / admin / home in die Backend-Komponente gerendert werden.

Ich habe einige Variationen ausprobiert, aber ich habe immer nicht / home oder / admin / home getroffen.

Bearbeiten - 19.04.2017

Da dieser Beitrag momentan viele Ansichten hat, habe ich ihn mit der endgültigen Lösung aktualisiert. Ich hoffe es hilft jemandem.

Bearbeiten - 08.05.2017

Alte Lösungen entfernt

Endgültige Lösung:

Dies ist die endgültige Lösung, die ich gerade verwende. Dieses Beispiel enthält auch eine globale Fehlerkomponente wie eine herkömmliche 404-Seite.

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
quelle
1
Vielen Dank, dass Sie Ihre Frage mit der endgültigen Antwort aktualisiert haben! Nur ein Vorschlag: Vielleicht könnten Sie nur die 4. und die erste Auflistung behalten, da die anderen veraltete Versionen der API verwenden und von der Antwort ablenken
Giuliano Vilela
lol, ich habe keine Ahnung, welches Format dieses Datum hat: 08.05.2017 Ich schlage vor, dass Sie das universelle ISO8601-Format für Daten verwenden, wenn Sie die Leute nicht verwirren möchten. ist 08 der Monat oder Tag? ISO8601 = Jahr.Monat.Tag Stunde.Minute.Sekunde (zunehmend detaillierter)
Wesm
Schöne aktualisierte endgültige Lösung, aber ich denke, Sie brauchen die previousLocationLogik nicht.
Tudorpavel
Was ist die Motivation für das vollständige Umschreiben des Reaktionsrouters? Es ist besser ein guter Grund
Oliver Watkins
Es ist der deklarative Ansatz. So können Sie Ihre Routings so einrichten, als würden Sie Reaktionskomponenten verwenden.
Datum

Antworten:

318

In React-Router-v4 verschachteln Sie nicht <Routes />. Stattdessen steckst du sie in eine andere <Component />.


Zum Beispiel

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

soll werden

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

mit

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

Hier ist ein grundlegendes Beispiel direkt aus der React-Router- Dokumentation .

Lyubomir
quelle
Ich kann über Ihren Link im Basisbeispiel implementieren, aber wenn ich die URL manuell eingebe, funktioniert es nicht auf meinem localhost-Server. aber es tut auf Ihrem Beispiel. Andererseits funktioniert HashRouter richtig, wenn ich die URL manuell mit # eingebe. Haben Sie eine Idee, warum auf meinem localhost-Server der BrowserRouter nicht funktioniert, wenn ich die URL manuell eingebe?
himawan_r
8
Können Sie die Themenkomponente zu einer Klasse machen? und woher kommt der Parameter 'match'? im render ()?
user1076813
21
Scheint lächerlich, man kann nicht einfach to="exampleTopicId"mit dem ${match.url}impliziten Sein.
Greenimpala
8
Sie können verschachtelte Routen pro docs reacttraining.com/react-router/web/example/route-config haben . Dies würde eine zentralisierte Routenkonfiguration gemäß dem Thema in den Dokumenten ermöglichen. Überlegen Sie, wie verrückt es wäre, in einem größeren Projekt zu verwalten, wenn es nicht verfügbar ist.
JustDave
5
Dies sind keine verschachtelten Routen. Es handelt sich um ein einstufiges Routing, bei dem immer noch die Rendereigenschaft einer Route verwendet wird, die eine Funktionskomponente als Eingabe verwendet. Schauen Sie genauer hin, es gibt keine Verschachtelung im Sinne eines Reaktionsrouters <4. RouteWithSubRoutes ist eine Eins -Liste Liste der Routen, die Pattern Matching verwenden.
Lyubomir
102

react-router v6

Ein Update für 2020: Die kommende Version 6 wird verschachtelte RouteKomponenten enthalten, die Just Work ™ enthalten. Siehe Beispielcode in diesem Blogbeitrag .

Während sich die ursprüngliche Frage auf v4 / v5 bezieht , wird die richtige Antwort bei v6- Schiffen nur verwendet, wenn Sie können . Es ist derzeit in Alpha.


react-router v4 & v5

Es ist richtig, dass Sie zum Verschachteln von Routen diese in der untergeordneten Komponente der Route platzieren müssen.

Wenn Sie jedoch eine Inline-Syntax bevorzugen, anstatt Ihre Routen komponentenübergreifend aufzuteilen, können Sie der renderRequisite der Route, unter der Sie verschachteln möchten , eine funktionale Komponente bereitstellen .

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

Wenn Sie daran interessiert sind, warum die renderRequisite verwendet werden sollte und nicht die componentRequisite, liegt dies daran, dass die Inline-Funktionskomponente nicht bei jedem Rendern erneut bereitgestellt wird. Weitere Informationen finden Sie in der Dokumentation .

Beachten Sie, dass das Beispiel die verschachtelten Routen in ein Fragment einschließt . Vor React 16 können Sie <div>stattdessen einen Container verwenden.

davnicwil
quelle
16
Gott sei Dank ist die einzige Lösung, die irgendwie klar und sicher ist und wie erwartet funktioniert. Ich wünschte, Router 3 verschachtelte Routen wären zurück.
Merunas Grincalaitis
Dieser eine perfekte sieht aus wie eckige Route-Outlet
Partha Ranjan
4
Sie sollten match.pathnicht verwenden match.url. Ersteres wird normalerweise in der Route- pathRequisite verwendet. Letzteres, wenn Sie eine neue Route schieben (z. B. Link toProp)
nbkhope
50

Ich wollte nur erwähnen, dass sich React-Router v4 radikal geändert hatIch React seit diese Frage gestellt / beantwortet wurde.

Es gibt keine <Match>Komponente mehr! <Switch>soll sicherstellen, dass nur die erste Übereinstimmung gerendert wird. <Redirect>gut .. leitet zu einer anderen Route weiter. Verwenden oder weglassenexact weglassen, um eine Teilübereinstimmung ein- oder auszuschließen.

Siehe die Dokumente. Sie sind großartig. https://reacttraining.com/react-router/

Hier ist ein Beispiel, das hoffentlich zur Beantwortung Ihrer Frage verwendet werden kann.

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

Hoffe es hat geholfen, lass es mich wissen. Wenn dieses Beispiel Ihre Frage nicht gut genug beantwortet, sagen Sie es mir und ich werde sehen, ob ich es ändern kann.

jar0m1r
quelle
Es gibt keine exactauf Redirect reacttraining.com/react-router/web/api/Redirect. Das wäre viel sauberer als das <Route exact path="/" render={() => <Redirect to="/path" />} />, was ich stattdessen mache. Zumindest lässt es mich nicht mit TypeScript.
Filuren
2
Verstehe ich richtig, dass es (mehr) keine verschachtelten / untergeordneten Routen gibt? Muss ich die Basisroute in allen Routen duplizieren? Bietet React-Router 4 keine Hilfe für die wartbare Strukturierung von Routen?
Ville
5
@Ville Ich bin erstaunt; Hast du eine bessere Lösung gefunden? Ich möchte nicht überall Routen haben,
mein Gott
1
Dies funktioniert, aber stellen Sie sicher, dass der öffentliche Pfad in der Webpack-Konfiguration für die Datei bundle.js auf "/" gesetzt ist. Andernfalls funktionieren die verschachtelten Routen bei der Seitenaktualisierung nicht.
Vikrant
6

Etwas wie das.

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
quelle
6

Es ist mir gelungen, verschachtelte Routen zu definieren, indem ich verschachtelte Routen Switchvor der Root-Route umbrochen und definiert habe.

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

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

Asukiaaa
quelle
Durch die Neuordnung der Reihenfolge wurde mein Problem behoben, obwohl ich nicht weiß, ob dies Nebenwirkungen haben wird. aber jetzt arbeiten .. danke :)
Anbu369
2

Sie können so etwas wie Routes.js ausprobieren

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;

Ich denke, dass Sie das auch von hier aus erreichen können.

Aniruddh Agarwal
quelle
guter Punkt! Ich dachte genauso, als ich mit React nach der Entwicklung der Java Spring-Boot-App begann. Das einzige, was ich ändern würde, ist 'div' in 'Switch' in Routes.js. Und tbh, Sie können alle Routen in App.js definieren, aber sie beispielsweise in die Datei index.js einbinden (create-react-app)
Reborn
Ja, du hast recht! Ich habe auf diese Weise implementiert, deshalb erwähne ich diese Methode.
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
quelle