route(root, defaultRoute, routes)
Beschreibung
Ermöglicht das Wechseln zwischen "Seiten" innerhalb einer Single-Page-Anwendung (SPA).
var Home = {
view: function () {
return 'Welcome';
},
};
m.route(document.body, '/home', {
'/home': Home, // definiert `https://localhost/#!/home`
});
Pro Anwendung darf nur ein m.route
-Aufruf erfolgen.
Signatur
m.route(root, defaultRoute, routes)
Argument | Typ | Erforderlich | Beschreibung |
---|---|---|---|
root | Element | Ja | Ein DOM-Element, das als Elternknoten für den von Mithril verwalteten Teilbaum dient. |
defaultRoute | String | Ja | Die Route, zu der weitergeleitet wird, wenn die aktuelle URL keiner definierten Route entspricht. Beachte: Dies ist nicht die anfängliche Route. Die anfängliche Route wird durch die URL in der Adressleiste des Browsers bestimmt. |
routes | Object<String,Component|RouteResolver> | Ja | Ein Objekt, dessen Schlüssel Routen-Strings sind und dessen Werte entweder Komponenten oder ein RouteResolver sind. |
returns | Gibt undefined zurück. |
Statische Elemente
m.route.set
Leitet zu einer passenden Route um oder zur Standardroute, falls keine passende Route gefunden wird. Löst ein asynchrones Neuzeichnen aller Mount Points aus.
m.route.set(path, params, options)
Argument | Typ | Erforderlich | Beschreibung |
---|---|---|---|
path | String | Ja | Der Pfadname der Zielroute ohne Präfix. Der Pfad kann Parameter enthalten, die mit Werten aus params interpoliert werden. |
params | Object | Nein | Routing-Parameter. Falls path Platzhalter für Parameter enthält, werden die Eigenschaften dieses Objekts in den Pfad-String eingefügt. |
options.replace | Boolean | Nein | Legt fest, ob ein neuer Eintrag in der Browser-History erstellt oder der aktuelle Eintrag ersetzt wird. Standard: false . |
options.state | Object | Nein | Das state -Objekt für den zugrunde liegenden Aufruf von history.pushState / history.replaceState . Dieses State-Objekt wird in der history.state -Eigenschaft verfügbar und mit den Routing-Parametern zusammengeführt. Beachte, dass diese Option nur mit der PushState-API funktioniert und ignoriert wird, falls der Router in den Hashchange-Modus wechselt (z. B. wenn die PushState-API nicht verfügbar ist). |
options.title | String | Nein | Der title -String für den zugrunde liegenden Aufruf von history.pushState / history.replaceState . |
returns | Gibt undefined zurück. |
Beachte: Wenn .set
zusammen mit params
verwendet wird, muss die Route ebenfalls definiert sein:
var Article = {
view: function (vnode) {
return 'This is article ' + vnode.attrs.articleid;
},
};
m.route(document.body, {
'/article/:articleid': Article,
});
m.route.set('/article/:articleid', { articleid: 1 });
m.route.get
Liefert den zuletzt vollständig aufgelösten Routing-Pfad ohne Präfix. Dieser Pfad kann von dem in der Adressleiste angezeigten Pfad abweichen, solange eine asynchrone Route noch auf die Auflösung wartet.
path = m.route.get()
Argument | Typ | Erforderlich | Beschreibung |
---|---|---|---|
returns | String | Gibt den letzten vollständig aufgelösten Pfad zurück. |
m.route.prefix
Legt ein Router-Präfix fest. Das Router-Präfix ist ein Teil der URL, der die zugrunde liegende Strategie des Routers bestimmt.
m.route.prefix = prefix
Argument | Typ | Erforderlich | Beschreibung |
---|---|---|---|
prefix | String | Ja | Das Präfix, das die zugrunde liegende Routing-Strategie steuert, die von Mithril verwendet wird. |
Da es sich um eine einfache Eigenschaft handelt, kann sie sowohl gelesen als auch geschrieben werden.
m.route.Link
Diese Komponente erzeugt einen dynamischen, gerouteten Link. Sie generiert hauptsächlich a
-Links mit lokalen href
-Attributen, die das Route-Präfix berücksichtigen.
m(m.route.Link, { href: '/foo' }, 'foo');
// Sofern sich m.route.prefix nicht von der Standardstrategie geändert hat, wird Folgendes gerendert:
// <a href="#!/foo">foo</a>
Links unterstützen folgende spezielle Attribute:
selector
entspricht dem ersten Argument fürm
: Beliebige Selektoren sind gültig, auch Nicht-a
-Elemente.params
undoptions
entsprechen den gleichnamigen Argumenten inm.route.set
.disabled
: Beitrue
wird das Routing-Verhalten und gebundeneonclick
-Handler deaktiviert sowie eindata-disabled="true
-Attribut für Barrierefreiheit hinzugefügt; beia
-Elementen wirdhref
entfernt.
Das Routing-Verhalten lässt sich nicht über die Event-Handling-API unterbinden – verwenden Sie stattdessen disabled
.
m(
m.route.Link,
{
href: '/foo',
selector: 'button.large',
disabled: true,
params: { key: 'value' },
options: { replace: true },
},
'link name'
);
// Rendert zu:
// <button disabled aria-disabled="true" class="large">link name</button>
vnode = m(m.route.Link, attributes, children)
Argument | Typ | Erforderlich | Beschreibung |
---|---|---|---|
attributes.href | Object | Ja | Die Zielroute, zu der navigiert werden soll. |
attributes.disabled | Boolean | Nein | Deaktiviert das Element barrierefrei. |
attributes.selector | String|Object|Function | Nein | Ein Selektor für m , Standardwert ist "a" . |
attributes.options | Object | Nein | Setzt die options , die an m.route.set übergeben werden. |
attributes.params | Object | Nein | Setzt die params , die an m.route.set übergeben werden. |
attributes | Object | Nein | Alle anderen Attribute, die an m weitergeleitet werden sollen. |
children | Array<Vnode>|String|Number|Boolean | Nein | Child vnodes für diesen Link. |
returns | Vnode | Ein vnode. |
m.route.param
Ermittelt einen Route-Parameter der zuletzt vollständig aufgelösten Route. Ein Routenparameter ist ein Schlüssel-Wert-Paar und kann aus verschiedenen Quellen stammen:
- Route-Interpolationen (z. B. bei der Route
/users/:id
, die zu/users/1
aufgelöst wird, hat der Parameter den Schlüsselid
und den Wert"1"
) - Router-Querystrings (z. B. beim Pfad
/users?page=1
hat der Parameter den Schlüsselpage
und den Wert"1"
) history.state
(z. B. beihistory.state
mit{foo: "bar"}
hat der Parameter den Schlüsselfoo
und den Wert"bar"
)
value = m.route.param(key)
Argument | Typ | Erforderlich | Beschreibung |
---|---|---|---|
key | String | Nein | Ein Routen-Parameter-Name (z. B. id in Route /users/:id oder page in Pfad /users/1?page=3 oder ein Key in history.state ) |
returns | String|Object | Liefert den Wert zum angegebenen Schlüssel. Ohne Schlüsselangabe wird ein Objekt mit allen Interpolations-Schlüsseln zurückgegeben. |
Hinweis: In der onmatch
-Funktion eines RouteResolvers ist die neue Route noch nicht vollständig aufgelöst, sodass m.route.param()
die Parameter der vorherigen Route (falls vorhanden) liefert. onmatch
erhält die neuen Route-Parameter als Argument.
m.route.SKIP
Ein spezieller Wert, der von der onmatch
-Funktion eines Route Resolvers zurückgegeben werden kann, um zur nächsten passenden Route zu springen.
RouteResolver
Ein Route-Resolver ist ein Objekt ohne Komponentenstatus, das eine onmatch
- und/oder render
-Methode enthält. Beide Methoden sind optional, aber mindestens eine muss vorhanden sein.
Falls ein Objekt als Komponente erkennbar ist (durch eine view
-Methode oder als function
/class
), wird es auch mit onmatch
- oder render
-Methoden als Komponente behandelt. Da ein Route-Resolver keine Komponente ist, besitzt er keine Lifecycle-Methoden.
Als Faustregel gilt: RouteResolver sollten sich in derselben Datei wie der m.route
-Aufruf befinden, während Komponentendefinitionen in eigenen Modulen stehen sollten.
routeResolver = {onmatch, render}
Bei Verwendung von Komponenten kann man sich diese als eine Art "syntaktischen Zucker" für diesen Route Resolver vorstellen, vorausgesetzt, die Komponente ist Home
:
var routeResolver = {
onmatch: function () {
return Home;
},
render: function (vnode) {
return [vnode];
},
};
routeResolver.onmatch
Die onmatch
-Funktion wird aufgerufen, wenn der Router eine zu rendernde Komponente ermitteln muss. Sie wird einmal pro Router-Pfadänderung aufgerufen, aber nicht bei nachfolgenden Redraws auf demselben Pfad. Sie kann verwendet werden, um Logik auszuführen, bevor eine Komponente initialisiert wird (z. B. Authentifizierungslogik, Daten-Preloading, Redirection Analytics Tracking usw.).
Diese Methode ermöglicht es dir auch, asynchron zu definieren, welche Komponente gerendert wird, was sie für Code-Splitting und asynchrones Modul-Laden geeignet macht. Um eine Komponente asynchron zu rendern, gib ein Promise zurück, das zu einer Komponente aufgelöst wird.
Weitere Informationen zu onmatch
findest du im Abschnitt Erweiterte Komponentenauflösung
routeResolver.onmatch(args, requestedPath, route)
Argument | Typ | Beschreibung |
---|---|---|
args | Object | Die Routing-Parameter |
requestedPath | String | Der Router-Pfad, der von der letzten Routing-Aktion angefordert wurde, einschließlich interpolierter Routing-Parameter-Values, aber ohne das Präfix. Wenn onmatch aufgerufen wird, ist die Auflösung für diesen Pfad noch nicht abgeschlossen und m.route.get() gibt noch den vorherigen Pfad zurück. |
route | String | Der Router-Pfad, der von der letzten Routing-Aktion angefordert wurde, ohne interpolierte Routing-Parameter-Values |
returns | Component|\Promise<Component>|undefined | Gibt eine Komponente oder ein Promise zurück, das zu einer Komponente aufgelöst wird. |
Wenn onmatch
eine Komponente oder ein Promise zurückgibt, das zu einer Komponente aufgelöst wird, wird diese Komponente als vnode.tag
für das erste Argument in der render
-Methode des RouteResolvers verwendet. Andernfalls wird vnode.tag
auf "div"
gesetzt. Wenn die onmatch
-Methode weggelassen wird, ist vnode.tag
ebenfalls "div"
.
Wenn onmatch
ein Promise zurückgibt, das abgelehnt wird, leitet der Router zurück zu defaultRoute
. Du kannst dieses Verhalten überschreiben, indem du .catch
in der Promise-Kette aufrufst, bevor du sie zurückgibst.
routeResolver.render
Die render
-Methode wird bei jedem Redraw für eine passende Route aufgerufen. Sie ähnelt der view
-Methode in Komponenten und existiert, um die Komponentenzusammensetzung zu vereinfachen. Sie ermöglicht es auch, vom Standardverhalten von Mithril.js abzuweichen, bei dem der gesamte Subtree ersetzt wird.
vnode = routeResolver.render(vnode)
Argument | Typ | Beschreibung |
---|---|---|
vnode | Object | Ein vnode, dessen Attribute-Objekt Routing-Parameter enthält. Wenn onmatch keine Komponente oder ein Promise zurückgibt, das zu einer Komponente aufgelöst wird, ist das tag -Feld des vnodes standardmäßig "div" |
vnode.attrs | Object | Eine Map von URL-Parameter-Values |
returns | Array<Vnode>|Vnode | Die vnodes, die gerendert werden sollen. |
Der vnode
-Parameter entspricht m(Component, m.route.param())
, wobei Component
die aufgelöste Komponente für die Route ist (nach routeResolver.onmatch
) und m.route.param()
wie hier dokumentiert ist. Wenn du diese Methode weglässt, ist der Standard-Return-Value [vnode]
, verpackt in einem Fragment, sodass du Key-Parameter verwenden kannst. In Kombination mit einem :key
-Parameter wird es zu einem Single-Element Keyed Fragment, da es am Ende zu so etwas wie [m(Component, {key: m.route.param("key"), ...})]
gerendert wird.
Wie es funktioniert
Routing ist ein System, das die Erstellung von Single-Page-Applications (SPA) ermöglicht, d. h. Anwendungen, die von einer "Seite" zu einer anderen wechseln können, ohne eine vollständige Browser-Aktualisierung zu verursachen.
Es ermöglicht eine nahtlose Navigation und bewahrt gleichzeitig die Möglichkeit, jede Seite einzeln mit einem Lesezeichen zu versehen, sowie die Möglichkeit, die Anwendung über den History-Mechanismus des Browsers zu navigieren.
Routing ohne Seitenaktualisierungen wird teilweise durch die history.pushState
API ermöglicht. Mit dieser API ist es möglich, die vom Browser angezeigte URL nach dem Laden einer Seite programmgesteuert zu ändern. Es liegt jedoch in der Verantwortung des Anwendungsentwicklers, sicherzustellen, dass das Navigieren zu einer bestimmten URL aus einem kalten Zustand (z. B. einem neuen Tab) das entsprechende Markup rendert.
Routing-Strategien
Die Routingstrategie bestimmt, wie eine Bibliothek das Routing tatsächlich implementiert. Es gibt drei allgemeine Strategien, die verwendet werden können, um ein SPA-Routing-System zu implementieren, und jede hat unterschiedliche Einschränkungen:
m.route.prefix = '#!'
(Standard) – Verwenden des Fragment Identifier (auch bekannt als Hash)-Teils der URL. Eine URL, die diese Strategie verwendet, sieht typischerweise so aus:https://localhost/#!/page1
m.route.prefix = '?'
– Verwenden des Querystrings. Eine URL, die diese Strategie verwendet, sieht typischerweise so aus:https://localhost/?/page1
m.route.prefix = ''
– Verwenden des Pfadnamens. Eine URL, die diese Strategie verwendet, sieht typischerweise so aus:https://localhost/page1
Die Hash-Strategie funktioniert garantiert in Browsern, die history.pushState
nicht unterstützen, da sie auf onhashchange
zurückgreifen kann. Verwende diese Strategie, wenn du die Hashes rein lokal halten möchtest.
Die Querystring-Strategie ermöglicht die Erkennung auf Serverseite, wird aber nicht als normaler Pfad angezeigt. Verwenden Sie diese Strategie, wenn Sie verankerte Links serverseitig unterstützen und potenziell erkennen möchten, und Sie nicht in der Lage sind, die erforderlichen Änderungen zur Unterstützung der Pfadnamenstrategie vorzunehmen (z. B. wenn Sie Apache verwenden und Ihre .htaccess
-Datei nicht ändern können).
Die Pfadnamenstrategie erzeugt die klarsten URLs, erfordert aber, dass der Server so eingerichtet ist, dass er den Single-Page-Application-Code von jeder URL aus bereitstellt, zu der die Anwendung routen kann. Verwende diese Strategie, wenn du sauberer aussehende URLs möchtest.
Single-Page-Applications, die die Hash-Strategie verwenden, verwenden oft die Konvention, ein Ausrufezeichen nach dem Hash zu setzen, um anzuzeigen, dass sie den Hash als Routing-Mechanismus verwenden und nicht zum Verlinken mit Ankern. Der #!
-String wird als Hashbang bezeichnet.
Die Standardstrategie nutzt den Hashbang.
Typische Verwendung
Normalerweise musst du ein paar Komponenten erstellen, um Routen zuzuordnen:
var Home = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
var Page1 = {
view: function () {
return [m(Menu), m('h1', 'Page 1')];
},
};
Im obigen Beispiel gibt es zwei Komponenten: Home
und Page1
. Jede enthält ein Menü und etwas Text. Das Menü selbst wird als Komponente definiert, um Wiederholungen zu vermeiden:
var Menu = {
view: function () {
return m('nav', [
m(m.route.Link, { href: '/' }, 'Home'),
m(m.route.Link, { href: '/page1' }, 'Page 1'),
]);
},
};
Jetzt können wir Routen definieren und unsere Komponenten ihnen zuordnen:
m.route(document.body, '/', {
'/': Home,
'/page1': Page1,
});
Hier geben wir zwei Routen an: /
und /page1
, die ihre jeweiligen Komponenten rendern, wenn der Benutzer zu jeder URL navigiert.
Navigieren zu verschiedenen Routen
Im obigen Beispiel enthält die Menu
-Komponente zwei m.route.Link
-Elemente. Dadurch wird ein Element erstellt, standardmäßig ein <a>
-Element, das so konfiguriert ist, dass es bei einem Klick des Benutzers zu einer anderen Route navigiert. Die Navigation erfolgt nicht zu einer externen Seite, sondern innerhalb der Anwendung.
Du kannst auch programmgesteuert navigieren, über m.route.set(route)
. Zum Beispiel m.route.set("/page1")
.
Beim Navigieren zwischen Routen wird das Router-Präfix für dich behandelt. Mit anderen Worten, lass den Hashbang #!
(oder welches Präfix du auch immer m.route.prefix
gesetzt hast) weg, wenn du Mithril.js-Routen verlinkst, sowohl in m.route.set
als auch in m.route.Link
.
Beachte, dass beim Navigieren zwischen Komponenten der gesamte Subtree ersetzt wird. Verwende einen Route Resolver mit einer render
-Methode, wenn du nur den Subtree patchen möchtest.
Routing-Parameter
Manchmal möchten wir eine variable ID oder ähnliche Daten in einer Route haben, aber wir möchten nicht explizit für jede mögliche ID eine separate Route angeben. Um dies zu erreichen, unterstützt Mithril.js parametrisierte Routen.
var Edit = {
view: function (vnode) {
return [m(Menu), m('h1', 'Editing ' + vnode.attrs.id)];
},
};
m.route(document.body, '/edit/1', {
'/edit/:id': Edit,
});
Im obigen Beispiel haben wir eine Route /edit/:id
definiert. Dies erstellt eine dynamische Route, die mit jeder URL übereinstimmt, die mit /edit/
beginnt und von einigen Daten gefolgt wird (z. B. /edit/1
, edit/234
usw.). Der Wert von id
wird dann als Attribut des vnodes der Komponente zugeordnet (vnode.attrs.id
)
Es ist möglich, mehrere Argumente in einer Route zu haben, z. B. /edit/:projectID/:userID
würde die Eigenschaften projectID
und userID
im Attribute-Objekt des vnodes der Komponente ergeben.
Key-Parameter
Wenn ein Benutzer von einer parametrisierten Route zur selben Route mit einem anderen Parameter navigiert (z. B. von /page/1
zu /page/2
bei einer Route /page/:id
), wird die Komponente nicht von Grund auf neu erstellt, da beide Routen zur selben Komponente aufgelöst werden und somit zu einem In-Place-Diff des virtuellen DOMs führen. Dies hat den Nebeneffekt, dass der onupdate
-Hook anstelle von oninit
/oncreate
ausgelöst wird. Es ist jedoch relativ üblich, dass ein Entwickler die Neuerstellung der Komponente mit dem Routenänderungsereignis synchronisieren möchte.
Um dies zu erreichen, ist es möglich, die Routenparametrisierung mit Keys für ein sehr praktisches Muster zu kombinieren:
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
Dies bedeutet, dass der vnode, der für die Root-Komponente der Route erstellt wird, ein Routenparameter-Objekt key
hat. Routenparameter werden zu attrs
im vnode. Wenn man also von einer Seite zur anderen springt, ändert sich der Schlüssel, was dazu führt, dass die Komponente von Grund auf neu erstellt wird, da der Schlüssel der Virtual-DOM-Engine signalisiert, dass es sich um unterschiedliche Entitäten handelt.
Du kannst diese Idee noch weiterführen, um Komponenten zu erstellen, die sich beim Neuladen selbst neu erstellen:
m.route.set(m.route.get(), {key: Date.now()})
Oder verwende sogar das History State
-Feature, um neu ladbare Komponenten zu erstellen, ohne die URL zu verunreinigen:
m.route.set(m.route.get(), null, {state: {key: Date.now()}})
Beachte, dass der Key-Parameter nur für Komponentenrouten funktioniert. Wenn du einen Route Resolver verwendest, musst du ein Single-Child Keyed Fragment verwenden und key: m.route.param("key")
übergeben, um dasselbe zu erreichen.
Variadische Routen
Es ist auch möglich, variadische Routen zu haben, d. h. eine Route mit einem Argument, das URL-Pfadnamen enthält, die Schrägstriche enthalten:
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': Edit,
});
Behandeln von 404s
Für isomorphe/universelle JavaScript-Anwendungen ist eine Kombination aus einem URL-Parameter und einer variadischen Route sehr nützlich, um eine benutzerdefinierte 404-Fehlerseite anzuzeigen.
Im Falle eines 404-Not-Found-Fehlers sendet der Server die benutzerdefinierte Seite an den Client zurück. Wenn Mithril.js geladen wird, leitet es den Client zur Standardroute um, da es nicht erkennen kann, dass diese Route existiert.
m.route(document.body, '/', {
'/': homeComponent,
// [...]
'/:404...': errorPageComponent,
});
History State
Es ist möglich, die zugrunde liegende history.pushState
-API voll auszunutzen, um die Navigationserfahrung des Benutzers zu verbessern. Beispielsweise könnte sich eine Anwendung den Zustand eines großen Formulars "merken", wenn der Benutzer eine Seite verlässt, indem er wegnavigiert, sodass der Benutzer beim Drücken der Zurück-Taste im Browser das Formular ausgefüllt hätte und nicht ein leeres Formular.
Beispielsweise könnte ein Formular wie folgt erstellt werden:
var state = {
term: '',
search: function () {
// save the state for this route
// this is equivalent to `history.replaceState({term: state.term}, null, location.href)`
m.route.set(m.route.get(), null, {
replace: true,
state: { term: state.term },
});
// navigate away
location.href = 'https://google.com/?q=' + state.term;
},
};
var Form = {
oninit: function (vnode) {
state.term = vnode.attrs.term||''; // populated from the `history.state` property if the user presses the back button
},
view: function () {
return m('form', [
m("input[placeholder='Search']", {
oninput: function (e) {
state.term = e.target.value;
},
value: state.term,
}),
m('button', { onclick: state.search }, 'Search'),
]);
},
};
m.route(document.body, '/', {
'/': Form,
});
Auf diese Weise bleibt das Eingabefeld mit dem Suchbegriff gefüllt, wenn der Benutzer sucht und dann die Zurück-Taste drückt, um zur Anwendung zurückzukehren. Diese Technik kann die Benutzererfahrung von großen Formularen und anderen Apps verbessern, bei denen nicht persistierter Zustand für einen Benutzer mühsam zu erzeugen ist.
Ändern des Router-Präfixes
Das Router-Präfix ist ein Teil der URL, der die zugrunde liegende Strategie des Routers bestimmt.
// set to pathname strategy
m.route.prefix = '';
// set to querystring strategy
m.route.prefix = '?';
// set to hash without bang
m.route.prefix = '#';
// set to pathname strategy on a non-root URL
// e.g. if the app lives under `https://localhost/my-app` and something else
// lives under `https://localhost`
m.route.prefix = '/my-app';
Fortgeschrittene Komponentenauflösung
Anstatt einer Route direkt eine Komponente zuzuweisen, können Sie ein RouteResolver
-Objekt angeben. Ein RouteResolver
-Objekt enthält eine onmatch()
- und/oder eine render()
-Methode. Beide Methoden sind optional, aber mindestens eine von ihnen muss vorhanden sein.
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath, route) {
return Home;
},
render: function (vnode) {
return vnode; // entspricht m(Home)
},
},
});
RouteResolver
sind nützlich, um eine Vielzahl komplexer Routing-Anwendungsfälle zu implementieren.
Ein Layout um eine Komponente wickeln
Oft ist es wünschenswert, die meisten gerouteten Komponenten in eine wiederverwendbare Hülle (oft als "Layout" bezeichnet) zu packen. Dazu müssen Sie zuerst eine Komponente erstellen, die das gemeinsame Markup enthält, das die verschiedenen Komponenten umschließt:
var Layout = {
view: function (vnode) {
return m('.layout', vnode.children);
},
};
Im obigen Beispiel besteht das Layout lediglich aus einem <div class="layout">
, das die an die Komponente übergebenen Kindelemente enthält. In der Praxis kann es jedoch so komplex sein, wie es die Anwendung erfordert.
Eine Möglichkeit, das Layout zu integrieren, besteht darin, eine anonyme Komponente in der Routen-Konfiguration zu definieren:
// Beispiel 1
m.route(document.body, '/', {
'/': {
view: function () {
return m(Layout, m(Home));
},
},
'/form': {
view: function () {
return m(Layout, m(Form));
},
},
});
Beachten Sie jedoch, dass die oberste Komponente anonym ist. Beim Wechsel von der /
-Route zur /form
-Route (oder umgekehrt) wird die anonyme Komponente entfernt. Das DOM wird dann von Grund auf neu erstellt. Wenn die Layout
-Komponente oninit
- und oncreate
-Lifecycle-Methoden hat, würden die oninit
- und oncreate
-Hooks bei jeder Routenänderung ausgelöst. Ob dies wünschenswert ist, hängt von der jeweiligen Anwendung ab.
Wenn Sie es vorziehen, dass die Layout
-Komponente "gedifft" und intakt gehalten wird, anstatt sie von Grund auf neu zu erstellen, sollten Sie stattdessen einen RouteResolver
als Root-Objekt verwenden:
// Beispiel 2
m.route(document.body, '/', {
'/': {
render: function () {
return m(Layout, m(Home));
},
},
'/form': {
render: function () {
return m(Layout, m(Form));
},
},
});
Beachten Sie, dass in diesem Fall die Layout
-Komponente oninit
- und oncreate
-Lifecycle-Methoden hat, diese nur bei der ersten Routenänderung ausgelöst würden (vorausgesetzt, alle Routen verwenden dasselbe Layout).
Um den Unterschied zwischen den beiden Beispielen zu verdeutlichen, ist Beispiel 1 äquivalent zu diesem Code:
// funktional äquivalent zu Beispiel 1
var Anon1 = {
view: function () {
return m(Layout, m(Home));
},
};
var Anon2 = {
view: function () {
return m(Layout, m(Form));
},
};
m.route(document.body, '/', {
'/': {
render: function () {
return m(Anon1);
},
},
'/form': {
render: function () {
return m(Anon2);
},
},
});
Da Anon1
und Anon2
unterschiedliche Komponenten sind, werden ihre Subtrees (einschließlich Layout
) von Grund auf neu erstellt. Dies geschieht auch, wenn Komponenten direkt ohne RouteResolver
verwendet werden.
In Beispiel 2, da Layout
die Komponente der obersten Ebene in beiden Routen ist, wird das DOM für die Layout
-Komponente verglichen (diffed) (d. h. intakt gelassen, wenn es keine Änderungen aufweist), und nur die Änderung von Home
zu Form
löst eine Neuerstellung dieses Abschnitts des DOM aus.
Weiterleitung
Mit dem onmatch
-Hook des RouteResolver
kann Logik ausgeführt werden, bevor die Komponente der obersten Ebene in einer Route initialisiert wird. Sie können entweder Mithrils m.route.set()
oder die native HTML history
-API verwenden. Bei der Weiterleitung mit der history
-API muss der onmatch
-Hook ein Promise zurückgeben, das niemals aufgelöst wird, um die Auflösung der übereinstimmenden Route zu verhindern. m.route.set()
bricht die Auflösung der übereinstimmenden Route intern ab, sodass dies nicht erforderlich ist.
Beispiel: Authentifizierung
Das folgende Beispiel zeigt die Implementierung einer Login-Wall, die verhindert, dass Benutzer die /secret
-Seite sehen, ohne sich anzumelden.
var isLoggedIn = false;
var Login = {
view: function () {
return m('form', [
m(
'button[type=button]',
{
onclick: function () {
isLoggedIn = true;
m.route.set('/secret');
},
},
'Login'
),
]);
},
};
m.route(document.body, '/secret', {
'/secret': {
onmatch: function () {
if (!isLoggedIn) m.route.set('/login');
else return Home;
},
},
'/login': Login,
});
Wenn die Anwendung geladen wird, wird onmatch
aufgerufen, und da isLoggedIn
false ist, leitet die Anwendung zu /login
weiter. Sobald der Benutzer die Login-Schaltfläche gedrückt hat, wird isLoggedIn
auf true gesetzt, und die Anwendung leitet zu /secret
weiter. Der onmatch
-Hook würde erneut ausgeführt, und da isLoggedIn
diesmal true ist, würde die Anwendung die Home
-Komponente rendern.
Im obigen Beispiel wird der Anmeldestatus des Benutzers der Einfachheit halber in einer globalen Variable gespeichert. Dieses Flag wird lediglich umgeschaltet, wenn der Benutzer auf die Login-Schaltfläche klickt. In einer realen Anwendung müsste ein Benutzer natürlich die richtigen Anmeldeinformationen angeben, und das Klicken auf die Login-Schaltfläche würde eine Anfrage an einen Server auslösen, um den Benutzer zu authentifizieren:
var Auth = {
username: '',
password: '',
setUsername: function (value) {
Auth.username = value;
},
setPassword: function (value) {
Auth.password = value;
},
login: function () {
m.request({
url: '/api/v1/auth',
params: { username: Auth.username, password: Auth.password },
}).then(function (data) {
localStorage.setItem('auth-token', data.token);
m.route.set('/secret');
});
},
};
var Login = {
view: function () {
return m('form', [
m('input[type=text]', {
oninput: function (e) {
Auth.setUsername(e.target.value);
},
value: Auth.username,
}),
m('input[type=password]', {
oninput: function (e) {
Auth.setPassword(e.target.value);
},
value: Auth.password,
}),
m('button[type=button]', { onclick: Auth.login }, 'Login'),
]);
},
};
m.route(document.body, '/secret', {
'/secret': {
onmatch: function () {
if (!localStorage.getItem('auth-token')) m.route.set('/login');
else return Home;
},
},
'/login': Login,
});
Vorabladen von Daten
Normalerweise kann eine Komponente Daten bei der Initialisierung laden. Dies führt dazu, dass die Komponente zweimal gerendert wird. Der erste Render-Pass erfolgt beim Routing, und der zweite wird nach Abschluss der Anfrage ausgelöst. Beachten Sie, dass loadUsers()
ein Promise zurückgibt, aber jedes von oninit
zurückgegebene Promise wird derzeit ignoriert. Der zweite Render-Pass stammt von der background
-Option für m.request
.
var state = {
users: [],
loadUsers: function () {
return m.request('/api/v1/users').then(function (users) {
state.users = users;
});
},
};
m.route(document.body, '/user/list', {
'/user/list': {
oninit: state.loadUsers,
view: function () {
return state.users.length > 0
? state.users.map(function (user) {
return m('div', user.id);
})
: 'loading';
},
},
});
Im obigen Beispiel zeigt die UI beim ersten Rendern "loading"
an, da state.users
vor Abschluss der Anfrage ein leeres Array ist. Sobald die Daten verfügbar sind, wird die UI neu gezeichnet und eine Liste von Benutzer-IDs angezeigt.
RouteResolver
können verwendet werden, um Daten vor dem Rendern einer Komponente vorab zu laden. Dadurch wird Bildschirmflackern vermieden und die Notwendigkeit einer Ladeanzeige umgangen.
var state = {
users: [],
loadUsers: function () {
return m.request('/api/v1/users').then(function (users) {
state.users = users;
});
},
};
m.route(document.body, '/user/list', {
'/user/list': {
onmatch: state.loadUsers,
render: function () {
return state.users.map(function (user) {
return m('div', user.id);
});
},
},
});
Oben wird render
erst nach Abschluss der Anfrage ausgeführt, wodurch der ternäre Operator überflüssig wird.
Code-Splitting
In großen Anwendungen kann es wünschenswert sein, den Code für jede Route bei Bedarf herunterzuladen, anstatt ihn im Voraus zu laden. Diese Art der Codeaufteilung wird als Code-Splitting oder Lazy Loading bezeichnet. In Mithril.js kann dies erreicht werden, indem ein Promise vom onmatch
-Hook zurückgegeben wird:
In seiner einfachsten Form könnte man Folgendes tun:
// Home.js
module.export = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
// index.js
function load(file) {
return m.request({
method: 'GET',
url: file,
extract: function (xhr) {
return new Function(
'var module = {};' + xhr.responseText + ';return module.exports;'
);
},
});
}
m.route(document.body, '/', {
'/': {
onmatch: function () {
return load('Home.js');
},
},
});
Damit dies jedoch in einem Produktionsmaßstab funktioniert, ist es realistischerweise erforderlich, alle Abhängigkeiten für das Home.js
-Modul in die Datei zu bündeln, die letztendlich vom Server bereitgestellt wird.
Glücklicherweise gibt es eine Reihe von Tools, die die Aufgabe des Bündelns von Modulen für Lazy Loading erleichtern. Hier ist ein Beispiel mit native dynamic import(...)
, das von vielen Bundlern unterstützt wird:
m.route(document.body, '/', {
'/': {
onmatch: function () {
return import('./Home.js');
},
},
});
Typisierte Routen
In bestimmten komplexen Routing-Fällen kann es sinnvoll sein, einen Wert stärker einzuschränken als nur den Pfad selbst, beispielsweise auf eine numerische ID. Sie können dies ganz einfach tun, indem Sie m.route.SKIP
von einer Route zurückgeben.
m.route(document.body, '/', {
'/view/:id': {
onmatch: function (args) {
if (!/^\d+$/.test(args.id)) return m.route.SKIP;
return ItemView;
},
},
'/view/:name': UserView,
});
Versteckte Routen
Es kann vorkommen, dass ein Benutzer einen bestimmten Benutzer nicht anzeigen darf. Anstatt einen Zugriffsfehler anzuzeigen, kann es in diesem Fall sinnvoll sein, so zu tun, als ob der Benutzer nicht existiert, und stattdessen zu einer 404-Seite weiterzuleiten. In diesem Fall können Sie m.route.SKIP
verwenden, um einfach so zu tun, als ob die Route nicht existiert.
m.route(document.body, '/', {
'/user/:id': {
onmatch: function (args) {
return Model.checkViewable(args.id).then(function (viewable) {
return viewable ? UserView : m.route.SKIP;
});
},
},
'/:404...': PageNotFound,
});
Routenabbruch / Blockierung
Der onmatch
-Hook des RouteResolver
kann die Routenauflösung verhindern, indem ein Promise zurückgegeben wird, das niemals aufgelöst wird. Dies kann verwendet werden, um versuchte redundante Routenauflösungen zu erkennen und abzubrechen:
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath) {
if (m.route.get() === requestedPath) return new Promise(function () {});
},
},
});
Integration von Drittanbietern
In bestimmten Situationen kann es erforderlich sein, mit einem anderen Framework wie React zusammenzuarbeiten. So geht's:
- Definieren Sie alle Ihre Routen wie gewohnt mit
m.route
, achten Sie jedoch darauf, es nur einmal zu verwenden. Mehrere Routenpunkte werden nicht unterstützt. - Wenn Sie Routing-Beobachter entfernen müssen, verwenden Sie
m.mount(root, null)
und verwenden Sie denselben Root, den Sie fürm.route(root, ...)
verwendet haben.m.route
verwendet internm.mount
, um alles zu verbinden.
Hier ist ein Beispiel mit React:
class Child extends React.Component {
constructor(props) {
super(props);
this.root = React.createRef();
}
componentDidMount() {
m.route(this.root, '/', {
// ...
});
}
componentDidUnmount() {
m.mount(this.root, null);
}
render() {
return <div ref={this.root} />;
}
}
Und hier ist das grobe Äquivalent mit Vue:
<div ref="root"></div>
Vue.component('my-child', {
template: `<div ref="root"></div>`,
mounted: function () {
m.route(this.$refs.root, '/', {
// ...
});
},
destroyed: function () {
m.mount(this.$refs.root, null);
},
});