Leveling up mit React: React Router

Avatar of Brad Westfall
Brad Westfall am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Dieses Tutorial ist das erste einer dreiteiligen Reihe über React von Brad Westfall. Als Brad mir dies vorschlug, wies er darauf hin, dass es zwar eine gute Anzahl von Tutorials zum Einstieg in React gibt, aber nicht so viele darüber, wie es danach weitergeht. Wenn Sie brandneu in React sind, empfehle ich, dieses Einführungs-Video anzusehen. Diese Reihe setzt dort an, wo die Grundlagen aufhören.

Artikelserie

  1. React Router (Du bist hier!)
  2. Container Components
  3. Redux

Warnung! Dieser Artikel wurde vor React Router 4 geschrieben, welches eine gängigere Wahl für Routing in React geworden ist. Hier gibt es einen neuen Artikel, der React Router 4 behandelt, den Sie unbedingt lesen sollten.

Als ich anfing zu lernen, fand ich viele Anfänger-Anleitungen (z. B. 1, 2, 3, 4), die zeigten, wie man einzelne Komponenten erstellt und sie im DOM rendert. Sie leisteten gute Arbeit beim Vermitteln der Grundlagen wie JSX und Props, aber ich hatte Schwierigkeiten herauszufinden, wie React im Gesamtbild funktioniert – wie eine echte Single Page Application (SPA). Da diese Reihe viel Stoff behandelt, wird sie nicht die absoluten Anfänger-Konzepte abdecken. Stattdessen wird sie davon ausgehen, dass Sie bereits verstehen, wie Sie mindestens eine Komponente erstellen und rendern.

Was es wert ist, hier sind einige andere großartige Anleitungen, die sich an Anfänger richten

Serien-Code

Diese Serie wird auch mit Code zum Spielen auf GitHub geliefert. Im Laufe der Serie werden wir eine grundlegende SPA erstellen, die sich auf Benutzer und Widgets konzentriert.

Um die Dinge einfach und kurz zu halten, beginnen die Beispiele in dieser Serie damit, dass React und React Router aus einem CDN bezogen werden. Sie werden also require() oder import in den folgenden Beispielen nicht sehen. Gegen Ende dieses Tutorials werden wir jedoch Webpack und Babel für die GitHub-Anleitungen einführen. Zu diesem Zeitpunkt ist alles ES6!

React-Router

React ist keine Framework, sondern eine Bibliothek. Daher löst es nicht alle Bedürfnisse einer Anwendung. Es leistet hervorragende Arbeit beim Erstellen von Komponenten und der Bereitstellung eines Systems zur Zustandsverwaltung, aber die Erstellung einer komplexeren SPA erfordert eine Unterstützung. Das erste, was wir uns ansehen werden, ist React Router.

Wenn Sie schon einmal einen Front-End-Router verwendet haben, werden Ihnen viele dieser Konzepte vertraut vorkommen. Aber im Gegensatz zu jedem anderen Router, den ich zuvor verwendet habe, verwendet React Router JSX, was anfangs vielleicht etwas seltsam aussieht.

Als Einführung, so sieht das Rendern einer einzelnen Komponente aus

var Home = React.createClass({
  render: function() {
    return (<h1>Welcome to the Home Page</h1>);
  }
});

ReactDOM.render((
  <Home />
), document.getElementById('root'));

So würde die Home-Komponente mit React Router gerendert werden

...

ReactDOM.render((
  <Router>
    <Route path="/" component={Home} />
  </Router>
), document.getElementById('root'));

Beachten Sie, dass <Router> und <Route> zwei verschiedene Dinge sind. Sie sind technisch gesehen React-Komponenten, aber sie erstellen selbst kein DOM. Auch wenn es so aussieht, als würde <Router> selbst im 'root' gerendert, definieren wir damit nur Regeln, wie unsere Anwendung funktioniert. Weiterhin werden Sie dieses Konzept oft sehen: Komponenten existieren manchmal nicht, um selbst DOM zu erstellen, sondern um andere Komponenten zu koordinieren, die das tun.

Im Beispiel definiert <Route> eine Regel, bei der das Besuchen der Startseite / die Home-Komponente im 'root' rendert.

Mehrere Routen

Im vorherigen Beispiel ist die einzelne Route sehr einfach. Sie bietet uns nicht viel Wert, da wir bereits die Möglichkeit hatten, die Home-Komponente ohne Beteiligung des Routers zu rendern.

Die Stärke von React Router zeigt sich, wenn wir mehrere Routen verwenden, um zu definieren, welche Komponente basierend auf welchem aktiven Pfad gerendert werden soll.

ReactDOM.render((
  <Router>
    <Route path="/" component={Home} />
    <Route path="/users" component={Users} />
    <Route path="/widgets" component={Widgets} />
  </Router>
), document.getElementById('root'));

Jede <Route> rendert ihre entsprechende Komponente, wenn ihr Pfad mit der URL übereinstimmt. Nur eine dieser drei Komponenten wird zu einem bestimmten Zeitpunkt in den 'root' gerendert. Mit dieser Strategie binden wir den Router einmal an den DOM-'root', und der Router tauscht dann Komponenten bei Routenänderungen aus.

Es ist auch erwähnenswert, dass der Router Routen wechselt, ohne Anfragen an den Server zu stellen. Stellen Sie sich also vor, jede Komponente könnte eine völlig neue Seite sein.

Wiederverwendbares Layout

Wir sehen die bescheidenen Anfänge einer Single Page Application. Sie löst jedoch immer noch keine realen Probleme. Sicher, wir könnten die drei Komponenten zu vollständigen HTML-Seiten aufbauen, aber was ist mit der Wiederverwendbarkeit von Code? Wahrscheinlich teilen sich diese drei Komponenten gemeinsame Assets wie einen Header und eine Seitenleiste, also wie verhindern wir HTML-Wiederholungen in jeder Komponente?

Stellen wir uns vor, wir würden eine Web-App bauen, die diesem Mockup ähnelt.

Ein einfaches Website-Mockup.

Wenn Sie darüber nachdenken, wie dieses Mockup in wiederverwendbare Abschnitte zerlegt werden kann, könnten Sie auf diese Idee kommen.

Wie Sie das einfache Web-Mockup in Abschnitte aufteilen könnten.

Denken in verschachtelbaren Komponenten und Layouts ermöglicht es uns, wiederverwendbare Teile zu erstellen.

Plötzlich teilt Ihnen die Kunstabteilung mit, dass die App eine Seite zum Suchen von Widgets benötigt, die der Seite zum Suchen von Benutzern ähnelt. Da sowohl **User List** als auch **Widget List** denselben "Look" für ihre Suchseite benötigen, macht die Idee, **Search Layout** als separate Komponente zu haben, jetzt noch mehr Sinn.

Suchen Sie jetzt nach Widgets anstelle von Benutzern, aber die übergeordneten Abschnitte bleiben gleich.

Search Layout kann jetzt eine Eltern-Vorlage für alle Arten von Suchseiten sein. Und während einige Seiten **Search Layout** benötigen, können andere **Main Layout** direkt ohne es verwenden.

Ein entkoppeltes Layout.

Dies ist eine gängige Strategie, und wenn Sie ein Templating-System verwendet haben, haben Sie wahrscheinlich etwas sehr Ähnliches getan. Lassen Sie uns nun die HTML-Bearbeitung angehen. Zuerst machen wir statisches HTML, ohne JavaScript zu berücksichtigen.

<div id="root">

  <!-- Main Layout -->
  <div class="app">
    <header class="primary-header"><header>
    <aside class="primary-aside"></aside>
    <main>

      <!-- Search Layout -->
      <div class="search">
        <header class="search-header"></header>
        <div class="results">

          <!-- User List -->
          <ul class="user-list">
            <li>Dan</li>
            <li>Ryan</li>
            <li>Michael</li>
          </ul>

        </div>
        <div class="search-footer pagination"></div>
      </div>

    </main>
  </div>

</div>

Denken Sie daran, dass das 'root'-Element immer vorhanden sein wird, da es das einzige Element ist, das der anfängliche HTML-Body hat, bevor JavaScript startet. Das Wort "root" ist passend, da unsere gesamte React-Anwendung daran gebunden wird. Aber es gibt keinen "richtigen Namen" oder Konvention, wie Sie es nennen. Ich habe "root" gewählt, also werden wir es weiterhin in den Beispielen verwenden. Aber seien Sie gewarnt, dass das direkte Binden an das <body>-Element stark abgeraten wird.

Nachdem Sie das statische HTML erstellt haben, konvertieren Sie es in React-Komponenten.

var MainLayout = React.createClass({
  render: function() {
    // Note the `className` rather than `class`
    // `class` is a reserved word in JavaScript, so JSX uses `className`
    // Ultimately, it will render with a `class` in the DOM
    return (
      <div className="app">
        <header className="primary-header"><header>
        <aside className="primary-aside"></aside>
        <main>
          {this.props.children}
        </main>
      </div>
    );
  }
});

var SearchLayout = React.createClass({
  render: function() {
    return (
      <div className="search">
        <header className="search-header"></header>
        <div className="results">
          {this.props.children}
        </div>
        <div className="search-footer pagination"></div>
      </div>
    );
  }
});

var UserList = React.createClass({
  render: function() {
    return (
      <ul className="user-list">
        <li>Dan</li>
        <li>Ryan</li>
        <li>Michael</li>
      </ul>
    );
  }
});

Lassen Sie sich nicht zu sehr davon ablenken, was ich "Layout" im Gegensatz zu "Komponente" nenne. Alle drei sind React-Komponenten. Ich nenne nur zwei davon "Layouts", da dies ihre Rolle ist.

Wir werden schließlich "verschachtelte Routen" verwenden, um UserList innerhalb von SearchLayout und dann innerhalb von MainLayout zu platzieren. Aber zuerst bemerken Sie, dass, wenn UserList innerhalb seines übergeordneten SearchLayout platziert wird, die übergeordnete Komponente this.props.children verwendet, um seine Position zu bestimmen. Alle Komponenten haben this.props.children als Prop, aber nur, wenn Komponenten verschachtelt sind, wird die übergeordnete Komponente von React automatisch mit diesem Prop gefüllt. Für Komponenten, die keine übergeordneten Komponenten sind, ist this.props.children null.

Verschachtelte Routen

Wie bekommen wir also diese Komponenten verschachtelt? Der Router erledigt das für uns, wenn wir Routen verschachteln.

ReactDOM.render((
  <Router>
    <Route component={MainLayout}>
      <Route component={SearchLayout}>
        <Route path="users" component={UserList} />
      </Route> 
    </Route>
  </Router>
), document.getElementById('root'));

Komponenten werden entsprechend der Verschachtelung der Routen durch den Router verschachtelt. Wenn der Benutzer die Route /users besucht, platziert React Router die UserList-Komponente innerhalb von SearchLayout und beide dann innerhalb von MainLayout. Das Endergebnis des Besuchs von /users sind die drei verschachtelten Komponenten, die im 'root' platziert sind.

Beachten Sie, dass wir keine Regel dafür haben, wann der Benutzer den Pfad der Startseite (/) besucht oder nach Widgets sucht. Diese wurden der Einfachheit halber weggelassen, aber lassen Sie uns sie mit dem neuen Router einfügen.

ReactDOM.render((
  <Router>
    <Route component={MainLayout}>
      <Route path="/" component={Home} />
      <Route component={SearchLayout}>
        <Route path="users" component={UserList} />
        <Route path="widgets" component={WidgetList} />
      </Route> 
    </Route>
  </Router>
), document.getElementById('root'));

Sie haben wahrscheinlich schon bemerkt, dass JSX den XML-Regeln folgt, im Sinne, dass die Route-Komponente entweder als ein Tag geschrieben werden kann: <Route /> oder zwei: <Route>...</Route>. Dies gilt für alle JSX, einschließlich Ihrer benutzerdefinierten Komponenten und normalen DOM-Knoten. Zum Beispiel ist <div /> gültiges JSX und wird beim Rendern zu <div></div>.

Der Kürze halber stellen Sie sich einfach vor, WidgetList ähnelt UserList.

Da <Route component={SearchLayout}> jetzt zwei Kind-Routen hat, kann der Benutzer /users oder /widgets besuchen und die entsprechende <Route> wird seine jeweiligen Komponenten innerhalb der SearchLayout-Komponente laden.

Beachten Sie auch, wie die Home-Komponente direkt in MainLayout platziert wird, ohne dass SearchLayout involviert ist – aufgrund der Art und Weise, wie die <Route>s verschachtelt sind. Sie können sich wahrscheinlich vorstellen, dass es einfach ist, die Verschachtelung von Layouts und Komponenten durch Umordnen der Routen zu ändern.

Index-Routen

React Router ist sehr ausdrucksstark und oft gibt es mehr als eine Möglichkeit, dasselbe zu tun. Wir könnten den obigen Router zum Beispiel auch so schreiben.

ReactDOM.render((
  <Router>
    <Route path="/" component={MainLayout}>
      <IndexRoute component={Home} />
      <Route component={SearchLayout}>
        <Route path="users" component={UserList} />
        <Route path="widgets" component={WidgetList} />
      </Route> 
    </Route>
  </Router>
), document.getElementById('root'));

Trotz seines anderen Aussehens funktionieren beide exakt auf die gleiche Weise.

Optionale Routen-Attribute

Manchmal hat <Route> ein component-Attribut ohne path, wie in der SearchLayout-Route von oben. Manchmal ist es notwendig, eine <Route> mit einem path und ohne component zu haben. Um zu sehen, warum, beginnen wir mit diesem Beispiel.

<Route path="product/settings" component={ProductSettings} />
<Route path="product/inventory" component={ProductInventory} />
<Route path="product/orders" component={ProductOrders} />

Der /product-Teil des path ist wiederholend. Wir können die Wiederholung entfernen, indem wir alle drei Routen in eine neue <Route> einpacken.

<Route path="product">
  <Route path="settings" component={ProductSettings} />
  <Route path="inventory" component={ProductInventory} />
  <Route path="orders" component={ProductOrders} />
</Route>

Auch hier zeigt React Router seine Ausdrucksstärke. Quizfrage: Haben Sie das Problem bei beiden Lösungen bemerkt? Im Moment haben wir keine Regeln dafür, wann der Benutzer den /product-Pfad besucht.

Um dies zu beheben, können wir eine IndexRoute hinzufügen.

<Route path="product">
  <IndexRoute component={ProductProfile} />
  <Route path="settings" component={ProductSettings} />
  <Route path="inventory" component={ProductInventory} />
  <Route path="orders" component={ProductOrders} />
</Route>

Verwenden Sie <Link>, nicht <a>

Beim Erstellen von Ankern für Ihre Routen müssen Sie <Link to=""> anstelle von <a href=""> verwenden. Aber keine Sorge, bei der Verwendung der <Link>-Komponente gibt Ihnen React Router letztendlich einen gewöhnlichen Anker im DOM. Die Verwendung von <Link> ist jedoch notwendig, damit React Router einige seiner Routing-Magie ausführen kann.

Fügen wir unserer MainLayout einige Links (Anker) hinzu.

var MainLayout = React.createClass({
  render: function() {
    return (
      <div className="app">
        <header className="primary-header"></header>
        <aside className="primary-aside">
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/users">Users</Link></li>
            <li><Link to="/widgets">Widgets</Link></li>
          </ul>
        </aside>
        <main>
          {this.props.children}
        </main>
      </div>
    );
  }
});

Attribute auf <Link>-Komponenten werden an den von ihnen erzeugten Anker weitergegeben. Also dieses JSX.

<Link to="/users" className="users">

Wird im DOM zu diesem.

<a href="/users" class="users">

Wenn Sie einen Anker für Nicht-Router-Pfade erstellen müssen, wie z. B. eine externe Website, verwenden Sie wie gewohnt normale Anker-Tags. Weitere Informationen finden Sie in der Dokumentation für IndexRoute und Link.

Aktive Links

Eine coole Funktion der <Link>-Komponente ist ihre Fähigkeit, zu wissen, wann sie aktiv ist.

<Link to="/users" activeClassName="active">Users</Link>

Wenn sich der Benutzer auf dem Pfad /users befindet, sucht der Router nach übereinstimmenden Ankern, die mit <Link> erstellt wurden, und schaltet ihre active-Klasse um. Mehr dazu erfahren Sie unter diesem Feature.

Browser-Verlauf

Zur Vermeidung von Verwirrung habe ich bis jetzt ein wichtiges Detail ausgelassen. Der <Router> muss wissen, welche History-Tracking-Strategie verwendet werden soll. Die React Router-Dokumentation empfiehlt browserHistory, die wie folgt implementiert ist.

var browserHistory = ReactRouter.browserHistory;

ReactDOM.render((
  <Router history={browserHistory}>
    ...
  </Router>
), document.getElementById('root'));

In früheren Versionen von React Router war das Attribut history nicht erforderlich und der Standard war die Verwendung von hashHistory. Wie der Name schon sagt, verwendete es ein #-Rautezeichen in der URL zur Verwaltung des Front-End-SPA-Routings, ähnlich wie man es von einem Backbone.js-Router erwarten würde.

Mit hashHistory sehen URLs so aus:

  • example.com
  • example.com/#/users?_k=ckuvup
  • example.com/#/widgets?_k=ckuvup

Was ist mit diesen hässlichen Query-Strings?

Wenn browserHistory implementiert ist, sehen die Pfade organischer aus.

  • example.com
  • example.com/users
  • example.com/widgets

Es gibt jedoch eine Einschränkung auf dem Server, wenn browserHistory auf der Client-Seite verwendet wird. Wenn der Benutzer seinen Besuch bei example.com beginnt und dann zu /users und /widgets navigiert, behandelt React Router dieses Szenario wie erwartet. Wenn der Benutzer jedoch seinen Besuch beginnt, indem er example.com/widgets direkt in den Browser eingibt oder auf example.com/widgets aktualisiert, muss der Browser mindestens eine Anfrage an den Server für /widgets stellen. Wenn es keinen serverseitigen Router gibt, führt dies zu einem 404.

Seien Sie vorsichtig mit URLs. Sie benötigen einen serverseitigen Router.

Um das 404-Problem vom Server aus zu lösen, empfiehlt React Router einen Wildcard-Router auf der Serverseite. Mit dieser Strategie sollte der Server unabhängig davon, welcher serverseitige Pfad aufgerufen wird, immer dieselbe HTML-Datei bereitstellen. Wenn der Benutzer dann direkt mit example.com/widgets beginnt, gibt der Server zwar dieselbe HTML-Datei zurück, aber React Router ist schlau genug, die richtige Komponente zu laden.

Der Benutzer wird nichts Seltsames bemerken, aber Sie haben möglicherweise Bedenken, immer dieselbe HTML-Datei bereitzustellen. In Codebeispielen wird diese Serie weiterhin die "Wildcard-Router"-Strategie verwenden, aber es liegt an Ihnen, Ihre serverseitige Weiterleitung so zu handhaben, wie Sie es für richtig halten.

Kann React Router sowohl auf der Server- als auch auf der Client-Seite in einem isomorphen Way verwendet werden? Klar, aber das ist weit über den Umfang dieses Tutorials hinaus.

Umleitung mit browserHistory

Das browserHistory-Objekt ist ein Singleton, sodass Sie es in jede Ihrer Dateien einbinden können. Wenn Sie den Benutzer manuell in Ihrem Code umleiten müssen, können Sie dessen push-Methode verwenden, um dies zu tun.

browserHistory.push('/some/path');

Routenabgleich

React Router behandelt Routenabgleich ähnlich wie andere Router.

<Route path="users/:userId" component={UserProfile} />

Diese Route wird übereinstimmen, wenn der Benutzer einen beliebigen Pfad besucht, der mit users/ beginnt und danach einen beliebigen Wert hat. Sie wird /users/1, /users/143 oder sogar /users/abc (was Sie selbst validieren müssen) übereinstimmen.

React Router übergibt den Wert für :userId als Prop an die UserProfile. Dieser Prop wird innerhalb von UserProfile als this.props.params.userId zugegriffen.

Router-Demo

An diesem Punkt haben wir genügend Code, um eine Demo zu zeigen.

Siehe den Pen React-Router Demo von Brad Westfall (@bradwestfall) auf CodePen.

Wenn Sie auf ein paar Routen in dem Beispiel geklickt haben, bemerken Sie vielleicht, dass die Zurück- und Vorwärts-Schaltflächen des Browsers mit dem Router funktionieren. Dies ist einer der Hauptgründe, warum diese history-Strategien existieren. Beachten Sie auch, dass bei jeder besuchten Route keine Anfragen an den Server gestellt werden, außer der allerersten, um das anfängliche HTML zu erhalten. Wie cool ist das?

ES6

In unserem CodePen-Beispiel sind React, ReactDOM und ReactRouter globale Variablen von einem CDN. Innerhalb des ReactRouter-Objekts befinden sich alle benötigten Dinge wie die Router- und Route-Komponenten. Wir könnten ReactRouter also so verwenden.

ReactDOM.render((
  <ReactRouter.Router>
    <ReactRouter.Route ... />
  </ReactRouter.Router>
), document.getElementById('root'));

Hier müssen wir alle unsere Router-Komponenten mit ihrem übergeordneten Objekt ReactRouter präfixieren. Oder wir könnten die neue Destrukturierungs-Syntax von ES6 wie folgt verwenden.

var { Router, Route, IndexRoute, Link } = ReactRouter

Dies "extrahiert" Teile von ReactRouter in normale Variablen, sodass wir direkt darauf zugreifen können.

Ab jetzt werden die Beispiele in dieser Reihe eine Vielzahl von ES6-Syntaxen verwenden, einschließlich Destrukturierung, dem Spread-Operator, Imports/Exports und vielleicht anderen. Zu jeder neuen Syntax wird eine kurze Erklärung gegeben, und das GitHub-Repository, das mit dieser Serie geliefert wird, enthält ebenfalls viele ES6-Erklärungen.

Bundling mit webpack und Babel

Wie bereits erwähnt, wird diese Serie mit einem GitHub-Repository geliefert, damit Sie mit Code experimentieren können. Da es dem Aufbau einer realen SPA ähneln wird, werden Tools wie webpack und Babel verwendet.

  • webpack bündelt mehrere JavaScript-Dateien zu einer einzigen Datei für den Browser.
  • Babel konvertiert ES6 (ES2015)-Code in ES5, da die meisten Browser noch nicht alles von ES6 verstehen. Da dieser Artikel älter wird, werden Browser ES6 unterstützen und Babel wird möglicherweise nicht benötigt.

Wenn Sie sich mit der Verwendung dieser Tools noch nicht wohlfühlen, machen Sie sich keine Sorgen, der Beispielcode hat alles bereits eingerichtet, sodass Sie sich auf React konzentrieren können. Aber lesen Sie unbedingt die Datei README.md des Beispielcodes für zusätzliche Workflow-Dokumentation.

Seien Sie vorsichtig mit veralteter Syntax

Wenn Sie bei Google nach Informationen zu React Router suchen, stoßen Sie möglicherweise auf viele Artikel oder StackOverflow-Seiten, die aus der Zeit vor der Version 1.0 von React Router stammen. Viele Funktionen aus der Vor-1.0-Version sind jetzt veraltet. Hier ist eine kurze Liste.

  • <Route name="" /> ist veraltet. Verwenden Sie stattdessen <Route path="" />.
  • <Route handler="" /> ist veraltet. Verwenden Sie stattdessen <Route component="" />.
  • <NotFoundRoute /> ist veraltet. Siehe Alternative
  • <RouteHandler /> ist veraltet.
  • willTransitionTo ist veraltet. Siehe onEnter
  • willTransitionFrom ist veraltet. Siehe onLeave
  • "Locations" werden jetzt "histories" genannt.

Siehe die vollständige Liste für 1.0.0 und 2.0.0

Zusammenfassung

Es gibt noch weitere Funktionen in React Router, die nicht gezeigt wurden. Schauen Sie sich also unbedingt die API-Dokumentation an. Die Ersteller von React Router haben auch ein Schritt-für-Schritt-Tutorial für React Router erstellt und schauen Sie sich dieses React.js Conf Video an, wie React Router erstellt wurde.

Besonderer Dank geht an Lynn Fisher für die Grafiken @lynnandtonic


Artikelserie

  1. React Router (Du bist hier!)
  2. Container Components
  3. Redux