route(root, defaultRoute, routes)
Descrizione
Permette la navigazione tra le diverse "pagine" di un'applicazione.
var Home = {
view: function () {
return 'Benvenuto';
},
};
m.route(document.body, '/home', {
'/home': Home, // definisce `https://localhost/#!/home`
});
È possibile avere una sola chiamata m.route
per applicazione.
Firma
m.route(root, defaultRoute, routes)
Argomento | Tipo | Richiesto | Descrizione |
---|---|---|---|
root | Element | Sì | Un elemento DOM che sarà il nodo padre del sottoalbero. |
defaultRoute | String | Sì | La route a cui reindirizzare se l'URL corrente non corrisponde a nessuna route definita. Nota: questa non è la route iniziale. La route iniziale corrisponderà all'URL presente nella barra degli indirizzi. |
routes | Object<String,Component|RouteResolver> | Sì | Un oggetto le cui chiavi sono stringhe di route e i cui valori sono componenti o un RouteResolver. |
restituisce | Restituisce undefined . |
Membri statici
m.route.set
Reindirizza a una route corrispondente o alla route predefinita se non è possibile trovare una corrispondenza. Innesca un ridisegno asincrono di tutti i punti di mount.
m.route.set(path, params, options)
Argomento | Tipo | Richiesto | Descrizione |
---|---|---|---|
path | String | Sì | Il nome del percorso a cui indirizzare, senza prefisso. Il percorso può includere parametri, interpolati con valori da params . |
params | Object | No | Parametri di routing. Se path contiene dei segnaposto per i parametri di routing, le proprietà di questo oggetto vengono interpolate nella stringa del percorso. |
options.replace | Boolean | No | Indica se creare una nuova voce nella cronologia del browser o sostituire quella corrente. Il valore predefinito è false . |
options.state | Object | No | L'oggetto state da passare alla chiamata sottostante history.pushState / history.replaceState . Questo oggetto state diventa disponibile nella proprietà history.state e viene unito nell'oggetto parametri di routing. Si noti che questa opzione è valida solo quando si utilizza l'API pushState e viene ignorata se il router ricade nella modalità hashchange (ovvero, se l'API pushState non è disponibile). |
options.title | String | No | La stringa title da passare alla chiamata sottostante history.pushState / history.replaceState . |
restituisce | Restituisce undefined . |
Ricorda che quando usi .set
con params
devi anche definire la route corrispondente:
var Article = {
view: function (vnode) {
return 'Questo è l\'articolo ' + vnode.attrs.articleid;
},
};
m.route(document.body, {
'/article/:articleid': Article,
});
m.route.set('/article/:articleid', { articleid: 1 });
m.route.get
Restituisce l'ultimo percorso di routing completamente risolto, escludendo il prefisso. Potrebbe differire dal percorso visualizzato nella barra degli indirizzi mentre una route asincrona è in attesa di risoluzione.
path = m.route.get()
Argomento | Tipo | Richiesto | Descrizione |
---|---|---|---|
restituisce | String | Restituisce l'ultimo percorso completamente risolto. |
m.route.prefix
Definisce un prefisso del router. Il prefisso del router è una porzione dell'URL che determina la strategia sottostante utilizzata dal router.
m.route.prefix = prefix
Argomento | Tipo | Richiesto | Descrizione |
---|---|---|---|
prefix | String | Sì | Il prefisso che controlla la strategia di routing sottostante utilizzata da Mithril. |
Questa è una proprietà semplice, quindi puoi sia leggerla che scriverla.
m.route.Link
Questo componente crea un link dinamico gestito dal routing. La sua funzione principale è generare link a
con href
locali adattati in base al prefisso della route.
m(m.route.Link, { href: '/foo' }, 'foo');
// A meno che m.route.prefix non sia cambiato rispetto alla strategia predefinita, esegui il rendering di:
// <a href="#!/foo">foo</a>
I link accettano una selezione di attributi speciali:
selector
è ciò che verrebbe passato come primo argomento am
: qualsiasi selettore è valido, inclusi gli elementi nona
.params
&options
sono gli argomenti con gli stessi nomi definiti inm.route.set
.disabled
, setrue
, disabilita il comportamento di routing e qualsiasi gestoreonclick
associato, e allega un attributodata-disabled="true"
per suggerimenti di accessibilità; se l'elemento è una
, l'href
viene rimosso.
Il comportamento di routing non può essere interrotto tramite l'API di gestione degli eventi; utilizzare invece l'attributo disabled
.
m(
m.route.Link,
{
href: '/foo',
selector: 'button.large',
disabled: true,
params: { key: 'value' },
options: { replace: true },
},
'link name'
);
// Esegue il rendering di:
// <button disabled aria-disabled="true" class="large">link name</button>
vnode = m(m.route.Link, attributes, children)
Argomento | Tipo | Richiesto | Descrizione |
---|---|---|---|
attributes.href | Object | Sì | La route di destinazione a cui navigare. |
attributes.disabled | Boolean | No | Disabilita l'elemento in modo accessibile. |
attributes.selector | String|Object|Function | No | Un selettore per m , il valore predefinito è "a" . |
attributes.options | Object | No | Imposta le options passate a m.route.set . |
attributes.params | Object | No | Imposta i params passati a m.route.set . |
attributes | Object | No | Qualsiasi altro attributo da inoltrare a m . |
children | Array<Vnode>|String|Number|Boolean | No | vnode figlio per questo link. |
restituisce | Vnode | Un vnode. |
m.route.param
Recupera un parametro di routing dall'ultima route completamente risolta. Un parametro di routing è una coppia chiave-valore. I parametri di route possono provenire da diversi punti:
- interpolazioni di route (ad esempio, se una route è
/users/:id
e si risolve in/users/1
, il parametro di route ha una chiaveid
e un valore"1"
) - querystring del router (ad esempio, se il percorso è
/users?page=1
, il parametro di route ha una chiavepage
e un valore"1"
) history.state
(ad esempio, sehistory.state
è{foo: "bar"}
, il parametro di route ha chiavefoo
e valore"bar"
)
value = m.route.param(key)
Argomento | Tipo | Richiesto | Descrizione |
---|---|---|---|
key | String | No | Un nome di parametro di route (ad esempio, id nella route /users/:id o page nel percorso /users/1?page=3 o una chiave in history.state ). |
restituisce | String|Object | Restituisce un valore per la chiave specificata. Se non viene specificata una chiave, restituisce un oggetto contenente tutte le chiavi di interpolazione. |
Si noti che nella funzione onmatch
di un RouteResolver
, la nuova route non è ancora stata completamente risolta e m.route.param()
restituirà i parametri della route precedente, se presenti. onmatch
riceve i parametri della nuova route come argomento.
m.route.SKIP
Un valore speciale che può essere restituito da un onmatch
di un route resolver per passare alla route successiva.
RouteResolver
Un RouteResolver
è un oggetto non-componente che contiene un metodo onmatch
e/o un metodo render
. Entrambi i metodi sono opzionali, ma almeno uno deve essere presente.
Se un oggetto può essere rilevato come componente (dalla presenza di un metodo view
o essendo una function
/class
), verrà trattato come tale anche se ha metodi onmatch
o render
. Poiché un RouteResolver
non è un componente, non ha metodi del ciclo di vita.
Come regola generale, i RouteResolver
devono trovarsi nello stesso file della chiamata m.route
, mentre le definizioni dei componenti devono trovarsi nei propri moduli.
routeResolver = {onmatch, render}
Quando si utilizzano i componenti, si può pensare a essi come a una forma di zucchero sintattico per questo route resolver; ad esempio, supponendo che il componente sia Home
:
var routeResolver = {
onmatch: function () {
return Home;
},
render: function (vnode) {
return [vnode];
},
};
routeResolver.onmatch
L'hook onmatch
viene chiamato quando il router deve trovare un componente da renderizzare. Viene invocato una sola volta per ogni modifica del percorso del router, ma non nei successivi ridisegni quando si rimane sullo stesso percorso. Può essere utilizzato per eseguire la logica prima che un componente si inizializzi (ad esempio, logica di autenticazione, precaricamento dei dati, tracciamento dell'analisi del reindirizzamento, ecc.).
Questo metodo permette anche di definire asincronamente quale componente verrà renderizzato, rendendolo adatto alla suddivisione del codice e al caricamento asincrono dei moduli. Per renderizzare un componente in modo asincrono, restituire una promise che si risolve in un componente.
Per ulteriori informazioni su onmatch
, vedere la sezione risoluzione avanzata dei componenti.
routeResolver.onmatch(args, requestedPath, route)
Argomento | Tipo | Descrizione |
---|---|---|
args | Object | I parametri di routing. |
requestedPath | String | Il percorso del router richiesto dall'ultima azione di routing, inclusi i valori dei parametri di routing interpolati, ma senza il prefisso. Quando viene chiamato onmatch , la risoluzione per questo percorso non è completa e m.route.get() restituisce ancora il percorso precedente. |
route | String | Il percorso del router richiesto dall'ultima azione di routing, esclusi i valori dei parametri di routing interpolati. |
restituisce | Component|\Promise<Component>|undefined | Restituisce un componente o una promise che si risolve in un componente. |
Se onmatch
restituisce un componente o una promise che si risolve in un componente, questo componente viene utilizzato come vnode.tag
per il primo argomento nel metodo render
del RouteResolver
. Altrimenti, vnode.tag
è impostato su "div"
. Allo stesso modo, se il metodo onmatch
viene omesso, anche vnode.tag
è "div"
.
Se onmatch
restituisce una promise che viene rifiutata, il router reindirizza a defaultRoute
. È possibile sovrascrivere questo comportamento chiamando .catch
sulla catena di promise prima di restituirla.
routeResolver.render
Il metodo render
viene chiamato a ogni ridisegno per una route corrispondente. È simile al metodo view
nei componenti ed esiste per semplificare la composizione dei componenti. Permette inoltre di evitare il comportamento predefinito di Mithril.js, che consiste nel sostituire l'intero sottoalbero.
vnode = routeResolver.render(vnode)
Argomento | Tipo | Descrizione |
---|---|---|
vnode | Object | Un vnode il cui oggetto attributi contiene parametri di routing. Se onmatch non restituisce un componente o una promise che si risolve in un componente, il campo tag del vnode è impostato di default su "div" . |
vnode.attrs | Object | Una mappa dei valori dei parametri URL. |
restituisce | Array<Vnode>|Vnode | I vnode da renderizzare. |
Il parametro vnode
è semplicemente m(Component, m.route.param())
dove Component
è il componente risolto per la route (dopo routeResolver.onmatch
) e m.route.param()
è come documentato qui. Se si omette questo metodo, il valore di ritorno predefinito è [vnode]
, racchiuso in un frammento in modo da poter utilizzare i parametri key. Combinato con un parametro :key
, diventa un frammento key singolo, poiché finisce per essere renderizzato in qualcosa come [m(Component, {key: m.route.param("key"), ...})]
.
Come funziona
Il routing è un sistema che permette di creare Single Page Applications (SPA), ovvero applicazioni in grado di passare da una "pagina" all'altra senza richiedere un aggiornamento completo del browser.
Consente una navigabilità senza interruzioni preservando la possibilità di aggiungere ai segnalibri ogni pagina individualmente e la possibilità di navigare nell'applicazione tramite il meccanismo di cronologia del browser.
Il routing senza aggiornamenti di pagina è reso parzialmente possibile dall'API history.pushState
. Utilizzando questa API, è possibile modificare programmaticamente l'URL visualizzato dal browser dopo che una pagina è stata caricata, ma è responsabilità dello sviluppatore dell'applicazione garantire che la navigazione a un determinato URL da uno stato "freddo" (ad esempio, una nuova scheda) renderizzi il markup appropriato.
Strategie di routing
La strategia di routing definisce come una libreria può implementare concretamente il routing. Esistono tre strategie generali che possono essere utilizzate per implementare un sistema di routing SPA e ognuna ha diverse avvertenze:
m.route.prefix = '#!'
(predefinito) – Utilizzo della porzione identificatore di frammento (aka hash) dell'URL. Un URL che utilizza questa strategia in genere ha un aspetto simile ahttps://localhost/#!/page1
.m.route.prefix = '?'
– Utilizzo della querystring. Un URL che utilizza questa strategia in genere ha un aspetto simile ahttps://localhost/?/page1
.m.route.prefix = ''
– Utilizzo del pathname. Un URL che utilizza questa strategia in genere ha un aspetto simile ahttps://localhost/page1
.
L'utilizzo della strategia hash garantisce il funzionamento anche nei browser che non supportano history.pushState
, poiché è possibile ricorrere a onhashchange
. Utilizzare questa strategia se si desidera mantenere gli hash puramente locali.
La strategia querystring consente il rilevamento lato server, ma non viene visualizzata come un percorso normale. Utilizzare questa strategia se si desidera supportare e potenzialmente rilevare link ancorati lato server e non si è in grado di apportare le modifiche necessarie per supportare la strategia pathname (ad esempio, se si utilizza Apache e non si può modificare il file .htaccess
).
La strategia pathname produce gli URL dall'aspetto più pulito, ma richiede la configurazione del server per servire il codice dell'applicazione a pagina singola da ogni URL a cui l'applicazione può indirizzare. Utilizzare questa strategia se si desidera URL dall'aspetto più pulito.
Le applicazioni a pagina singola che utilizzano la strategia hash spesso utilizzano la convenzione di avere un punto esclamativo dopo l'hash per indicare che stanno utilizzando l'hash come meccanismo di routing e non allo scopo di collegarsi agli ancoraggi. La stringa #!
è nota come hashbang.
La strategia predefinita utilizza l'hashbang.
Utilizzo tipico
In genere, è necessario creare alcuni componenti per associare le route a:
var Home = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
var Page1 = {
view: function () {
return [m(Menu), m('h1', 'Page 1')];
},
};
Nell'esempio sopra, ci sono due componenti: Home
e Page1
. Ognuno contiene un menu e del testo. Il menu stesso viene definito come componente per evitare ripetizioni:
var Menu = {
view: function () {
return m('nav', [
m(m.route.Link, { href: '/' }, 'Home'),
m(m.route.Link, { href: '/page1' }, 'Page 1'),
]);
},
};
Ora possiamo definire le route e mappare i nostri componenti a esse:
m.route(document.body, '/', {
'/': Home,
'/page1': Page1,
});
Qui specifichiamo due route: /
e /page1
, che renderizzano i rispettivi componenti quando l'utente naviga verso ogni URL.
Navigazione verso route diverse
Nell'esempio sopra, il componente Menu
ha due m.route.Link
. Questo crea un elemento, di default un <a>
, e lo configura in modo che, se l'utente vi fa clic sopra, si navighi automaticamente verso un'altra route. Non naviga in remoto, solo localmente.
Puoi anche navigare programmaticamente, tramite m.route.set(route)
. Ad esempio, m.route.set("/page1")
.
Quando si naviga tra le route, il prefisso del router viene gestito automaticamente. In altre parole, ometti l'hashbang #!
(o qualsiasi prefisso tu abbia impostato m.route.prefix
su) quando colleghi le route Mithril.js, incluso sia in m.route.set
che in m.route.Link
.
Tieni presente che quando si naviga tra i componenti, l'intero sottoalbero viene sostituito. Utilizzare un route resolver con un metodo render
se si desidera solo applicare una patch al sottoalbero.
Parametri di routing
A volte si desidera che un ID variabile o dati simili compaiano in una route, ma non si vuole specificare esplicitamente una route separata per ogni possibile ID. Per raggiungere questo obiettivo, Mithril.js supporta route parametrizzate:
var Edit = {
view: function (vnode) {
return [m(Menu), m('h1', 'Editing ' + vnode.attrs.id)];
},
};
m.route(document.body, '/edit/1', {
'/edit/:id': Edit,
});
Nell'esempio sopra, abbiamo definito una route /edit/:id
. Questo crea una route dinamica che corrisponde a qualsiasi URL che inizia con /edit/
ed è seguito da alcuni dati (ad esempio, /edit/1
, edit/234
, ecc.). Il valore id
viene quindi mappato come attributo del vnode del componente (vnode.attrs.id
).
È possibile avere più argomenti in una route, ad esempio /edit/:projectID/:userID
produrrebbe le proprietà projectID
e userID
sull'oggetto attributi vnode del componente.
Parametro Key
Quando un utente naviga da una route parametrizzata alla stessa route con un parametro diverso (ad esempio, passando da /page/1
a /page/2
data una route /page/:id
), il componente non verrebbe ricreato da zero poiché entrambe le route si risolvono nello stesso componente e quindi si traducono in un virtual dom in-place diff. Questo ha come effetto collaterale l'attivazione dell'hook onupdate
, invece di oninit
/oncreate
. Tuttavia, è relativamente comune per uno sviluppatore voler sincronizzare la ricreazione del componente con l'evento di modifica della route.
Per raggiungere questo obiettivo, è possibile combinare la parametrizzazione della route con key per un modello molto conveniente:
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
Ciò significa che il vnode creato per il componente root della route ha un oggetto parametro di route key
. I parametri di route diventano attrs
nel vnode. Di conseguenza, quando si passa da una pagina all'altra, la key
cambia e ciò comporta la ricreazione del componente da zero (poiché la key
indica al motore del DOM virtuale che i componenti vecchi e nuovi sono entità distinte).
Puoi spingere ulteriormente questa idea per creare componenti che si ricreano quando vengono ricaricati:
m.route.set(m.route.get(), {key: Date.now()})
Oppure puoi persino utilizzare la funzionalità history state
per ottenere componenti ricaricabili senza inquinare l'URL:
m.route.set(m.route.get(), null, {state: {key: Date.now()}})
Si noti che il parametro key funziona solo per le route dei componenti. Se si utilizza un route resolver, è necessario utilizzare un frammento key singolo, passando key: m.route.param("key")
, per ottenere lo stesso risultato.
Route variadiche
È anche possibile avere route variadiche, ovvero una route con un argomento che contiene pathname URL che contengono barre:
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': Edit,
});
Gestione dei 404
Per un'app JavaScript isomorfica / universale, un parametro url e una route variadica combinati sono molto utili per visualizzare una pagina di errore 404 personalizzata.
In caso di errore 404 Not Found, il server restituisce la pagina personalizzata al client. Quando Mithril.js viene caricato, reindirizzerà il client alla route predefinita perché non può sapere quella route.
m.route(document.body, '/', {
'/': homeComponent,
// [...]
'/:404...': errorPageComponent,
});
History state
È possibile sfruttare appieno l'API history.pushState
sottostante per migliorare l'esperienza di navigazione dell'utente. Ad esempio, un'applicazione potrebbe "ricordare" lo stato di un modulo di grandi dimensioni quando l'utente abbandona una pagina, in modo che, se l'utente premesse il pulsante Indietro nel browser, ritroverebbe il modulo compilato anziché vuoto.
Ad esempio, potresti creare un modulo come questo:
var state = {
term: '',
search: function () {
// salva lo stato per questa route
// questo è equivalente a `history.replaceState({term: state.term}, null, location.href)`
m.route.set(m.route.get(), null, {
replace: true,
state: { term: state.term },
});
// vai via
location.href = 'https://google.com/?q=' + state.term;
},
};
var Form = {
oninit: function (vnode) {
state.term = vnode.attrs.term||''; // // popolato dalla proprietà `history.state` se l'utente preme il pulsante indietro
},
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 }, 'Cerca'),
]);
},
};
m.route(document.body, '/', {
'/': Form,
});
In questo modo, se l'utente esegue una ricerca e preme il pulsante Indietro per tornare all'applicazione, l'input sarà ancora popolato con il termine di ricerca. Questa tecnica può migliorare l'esperienza utente di moduli di grandi dimensioni e altre app in cui lo stato non persistente è laborioso da produrre per un utente.
Modifica del prefisso del router
Il prefisso del router è una porzione dell'URL che determina la strategia sottostante utilizzata dal router.
// imposta la strategia del pathname
m.route.prefix = '';
// imposta la strategia della stringa di query
m.route.prefix = '?';
// imposta l'hash senza bang
m.route.prefix = '#';
// imposta la strategia del pathname su un URL non radice
// es. se l'app si trova sotto `https://localhost/my-app` e qualcos'altro si trova sotto `https://localhost`
m.route.prefix = '/my-app';
Gestione avanzata dei componenti
Invece di associare un componente a una route, puoi specificare un oggetto RouteResolver
. Un oggetto RouteResolver
contiene un metodo onmatch()
e/o un metodo render()
. Entrambi i metodi sono opzionali, ma almeno uno dei due deve essere presente.
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath, route) {
return Home;
},
render: function (vnode) {
return vnode; // equivalente a m(Home)
},
},
});
I RouteResolver
sono utili per implementare una varietà di casi d'uso avanzati di routing.
Avvolgimento di un componente layout
Spesso è utile racchiudere tutti o la maggior parte dei componenti gestiti tramite routing in un contenitore riutilizzabile (spesso chiamato "layout"). Per fare ciò, devi prima creare un componente che contenga il markup comune che avvolgerà i vari componenti:
var Layout = {
view: function (vnode) {
return m('.layout', vnode.children);
},
};
Nell'esempio precedente, il layout consiste semplicemente in un <div class="layout">
che contiene i figli passati al componente, ma in uno scenario reale potrebbe essere complesso quanto necessario.
Un modo per avvolgere il layout è definire un componente anonimo nella mappa delle route:
// example 1
m.route(document.body, '/', {
'/': {
view: function () {
return m(Layout, m(Home));
},
},
'/form': {
view: function () {
return m(Layout, m(Form));
},
},
});
Tuttavia, tieni presente che, poiché il componente di livello superiore è un componente anonimo, il passaggio dalla route /
alla route /form
(o viceversa) smonterà il componente anonimo e ricreerà il DOM da zero. Se il componente Layout
avesse metodi del ciclo di vita definiti, gli hook oninit
e oncreate
verrebbero eseguiti ad ogni cambio di route. A seconda dell'applicazione, questo potrebbe essere desiderabile o meno.
Se preferisci che il componente Layout
venga gestito e mantenuto intatto piuttosto che ricreato da zero, dovresti invece utilizzare un RouteResolver
come oggetto radice:
// example 2
m.route(document.body, '/', {
'/': {
render: function () {
return m(Layout, m(Home));
},
},
'/form': {
render: function () {
return m(Layout, m(Form));
},
},
});
Si noti che in questo caso, se il componente Layout
ha metodi del ciclo di vita oninit
e oncreate
, verrebbero eseguiti solo al primo cambio di route (supponendo che tutte le route utilizzino lo stesso layout).
Per chiarire la differenza tra i due esempi, l'esempio 1 è equivalente a questo codice:
// functionally equivalent to example 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);
},
},
});
Poiché Anon1
e Anon2
sono componenti diversi, i loro sottoalberi (incluso Layout
) vengono ricreati da zero. Questo è anche ciò che accade quando i componenti vengono utilizzati direttamente senza un RouteResolver
.
Nell'esempio 2, poiché Layout
è il componente di livello superiore in entrambe le route, il DOM per il componente Layout
viene differenziato (cioè lasciato intatto se non ha modifiche) e solo la modifica da Home
a Form
attiva una ricreazione di quella sottosezione del DOM.
Reindirizzamento
L'hook onmatch
del RouteResolver
può essere utilizzato per eseguire la logica prima che il componente di livello superiore in una route venga inizializzato. Puoi utilizzare m.route.set()
di Mithril o l'API history
nativa di HTML. Quando si reindirizza con l'API history
, l'hook onmatch
deve restituire una Promise
che non viene mai risolta per impedire la risoluzione della route corrispondente. m.route.set()
annulla internamente la risoluzione della route corrispondente, quindi questo non è necessario con essa.
Esempio: autenticazione
L'esempio seguente mostra come implementare una pagina di login obbligatoria che impedisce agli utenti di vedere la pagina /secret
a meno che non effettuino il login.
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,
});
Quando l'applicazione viene caricata, viene chiamato onmatch
e, poiché isLoggedIn
è false
, l'applicazione reindirizza a /login
. Una volta che l'utente ha premuto il pulsante di login, isLoggedIn
verrebbe impostato su true
e l'applicazione reindirizzerebbe a /secret
. L'hook onmatch
verrebbe eseguito nuovamente e, poiché isLoggedIn
è true
questa volta, l'applicazione renderizzerebbe il componente Home
.
Per semplicità, nell'esempio precedente, lo stato di accesso dell'utente viene mantenuto in una variabile globale e quel flag viene semplicemente modificato quando l'utente fa clic sul pulsante di login. In un'applicazione reale, un utente dovrebbe ovviamente fornire credenziali di accesso appropriate e fare clic sul pulsante di login attiverebbe una richiesta a un server per autenticare l'utente:
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,
});
Precaricamento dei dati
In genere, un componente può caricare i dati all'inizializzazione. Il caricamento dei dati in questo modo fa sì che il componente venga renderizzato due volte. Il primo passaggio di rendering si verifica al routing e il secondo si attiva al completamento della richiesta. Presta attenzione al fatto che loadUsers()
restituisce una Promise
, ma qualsiasi Promise
restituita da oninit
viene attualmente ignorata. Il secondo passaggio di rendering proviene dall'opzione background
per 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';
},
},
});
Nell'esempio precedente, al primo rendering, l'interfaccia utente mostra "loading"
poiché state.users
è un array vuoto prima che la richiesta venga completata. Quindi, una volta che i dati sono disponibili, l'interfaccia utente viene aggiornata e viene visualizzato un elenco di ID utente.
I RouteResolver
possono essere utilizzati come meccanismo per precaricare i dati prima di eseguire il rendering di un componente al fine di evitare lo sfarfallio dell'interfaccia utente e quindi bypassare la necessità di un indicatore di caricamento:
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);
});
},
},
});
Sopra, render
viene eseguito solo dopo il completamento della richiesta, rendendo superfluo l'operatore ternario.
Code splitting
In un'applicazione di grandi dimensioni, potrebbe essere utile scaricare il codice per ogni route su base richiesta, piuttosto che in anticipo. La divisione del codebase in questo modo è nota come code splitting o lazy loading. In Mithril.js, questo può essere realizzato restituendo una Promise
dall'hook onmatch
:
Nella sua forma più semplice, si potrebbe fare quanto segue:
// 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');
},
},
});
Tuttavia, realisticamente, affinché ciò funzioni su scala di produzione, sarebbe necessario includere tutte le dipendenze per il modulo Home.js
nel bundle che viene infine servito dal server.
Fortunatamente, ci sono una serie di strumenti che facilitano il compito di raggruppare i moduli per il lazy loading. Ecco un esempio che utilizza import(...)
dinamico nativo, supportato da molti bundler:
m.route(document.body, '/', {
'/': {
onmatch: function () {
return import('./Home.js');
},
},
});
Route tipizzate
In alcuni casi di routing avanzati, potresti voler vincolare un valore ulteriormente rispetto al solo percorso stesso, corrispondendo solo a un ID numerico. Puoi farlo abbastanza facilmente restituendo m.route.SKIP
da una route.
m.route(document.body, '/', {
'/view/:id': {
onmatch: function (args) {
if (!/^\d+$/.test(args.id)) return m.route.SKIP;
return ItemView;
},
},
'/view/:name': UserView,
});
Route nascoste
In rare circostanze, potresti voler nascondere determinate route per alcuni utenti, ma non per tutti gli altri. Ad esempio, a un utente potrebbe essere vietato visualizzare un particolare utente e, invece di mostrare un errore di autorizzazione, preferiresti simulare che non esista e reindirizzare invece a una vista 404. In questo caso, puoi usare m.route.SKIP
per simulare che la route non esista.
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,
});
Annullamento / blocco della route
L'hook onmatch
di RouteResolver
può impedire il completamento della route restituendo una Promise
che non viene mai risolta. Questo può essere usato per identificare tentativi ridondanti di risoluzione delle route e annullarli:
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath) {
if (m.route.get() === requestedPath) return new Promise(function () {});
},
},
});
Integrazione di terze parti
In determinate situazioni, potresti aver bisogno di interoperare con un altro framework come React. Ecco come si fa:
- Definisci tutte le tue route usando
m.route
come al solito, ma assicurati di usarlo solo una volta. Non è supportato l'utilizzo di più punti di route. - Quando devi rimuovere le sottoscrizioni di routing, usa
m.mount(root, null)
, usando la stessa root su cui hai usatom.route(root, ...)
.m.route
usam.mount
internamente per collegare tutto, quindi non c'è nulla di magico.
Ecco un esempio con 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} />;
}
}
Ed ecco l'equivalente approssimativo con 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);
},
});