route(root, defaultRoute, routes)
Description
Permet de naviguer entre les "pages" d'une application.
var Home = {
view: function () {
return 'Bienvenue';
},
};
m.route(document.body, '/home', {
'/home': Home, // définit `https://localhost/#!/home`
});
Vous ne pouvez effectuer qu'un seul appel à m.route
par application.
Signature
m.route(root, defaultRoute, routes)
Argument | Type | Required | Description |
---|---|---|---|
root | Element | Yes | Un élément DOM qui sera le nœud parent du sous-arbre. |
defaultRoute | String | Yes | La route vers laquelle rediriger si l'URL actuelle ne correspond à aucune route définie. Notez que ce n'est pas la route par défaut au démarrage. La route initiale est déterminée par l'URL de la barre d'adresse. |
routes | Object<String,Component|RouteResolver> | Yes | Un objet dont les clés sont des chaînes de caractères représentant les routes et les valeurs sont soit des composants, soit un RouteResolver. |
returns | Renvoie undefined . |
Static members
m.route.set
Redirige vers une route correspondante, ou vers la route par défaut si aucune route correspondante n'est trouvée. Déclenche un redessin asynchrone de tous les points de montage.
m.route.set(path, params, options)
Argument | Type | Required | Description |
---|---|---|---|
path | String | Yes | Le nom du chemin vers lequel rediriger, sans le préfixe. Le chemin peut inclure des paramètres, qui seront interpolés avec les valeurs provenant de params . |
params | Object | No | Paramètres de routage. Si path contient des paramètres de routage, les propriétés de cet objet sont utilisées pour interpoler la chaîne de caractères du chemin. |
options.replace | Boolean | No | Indique s'il faut créer une nouvelle entrée dans l'historique du navigateur ou remplacer l'entrée actuelle. La valeur par défaut est false . |
options.state | Object | No | L'objet state à passer à l'appel sous-jacent history.pushState / history.replaceState . Cet objet d'état devient disponible dans la propriété history.state et est fusionné dans l'objet des paramètres de routage. Notez que cette option ne fonctionne que lorsque vous utilisez l'API pushState , et est ignorée si le routeur revient au mode hashchange (c'est-à-dire si l'API pushState n'est pas disponible). |
options.title | String | No | La chaîne de caractères title à passer à l'appel sous-jacent history.pushState / history.replaceState . |
returns | Renvoie undefined . |
N'oubliez pas que lorsque vous utilisez .set
avec params
, vous devez également définir la route :
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
Retourne le dernier chemin de routage entièrement résolu, sans le préfixe. Il peut différer du chemin affiché dans la barre d'adresse pendant qu'une route asynchrone est en attente de résolution.
path = m.route.get()
Argument | Type | Required | Description |
---|---|---|---|
returns | String | Retourne le dernier chemin entièrement résolu. |
m.route.prefix
Définit un préfixe de routeur. Le préfixe de routeur est un fragment de l'URL qui détermine la stratégie sous-jacente utilisée par le routeur.
m.route.prefix = prefix
Argument | Type | Required | Description |
---|---|---|---|
prefix | String | Yes | Le préfixe qui contrôle la stratégie de routage sous-jacente utilisée par Mithril. |
Il s'agit d'une simple propriété, vous pouvez donc à la fois la lire et l'écrire.
m.route.Link
Ce composant crée un lien routé dynamique. Sa fonction essentielle est de produire des liens a
avec des attributs href
locaux transformés pour tenir compte du préfixe de route.
m(m.route.Link, { href: '/foo' }, 'foo');
// Sauf si `m.route.prefix` a été modifié par rapport à la stratégie par défaut, le rendu sera :
// <a href="#!/foo">foo</a>
Les liens acceptent une sélection d'attributs spéciaux :
selector
est ce qui serait passé comme premier argument àm
: tout sélecteur est valide, y compris les éléments autres quea
.params
&options
sont les arguments avec les mêmes noms que ceux définis dansm.route.set
.disabled
, si la valeur esttrue
, désactive le comportement de routage et tout gestionnaireonclick
lié, et attache un attributdata-disabled="true"
pour les indications d'accessibilité ; si l'élément est una
, l'attributhref
est supprimé.
Le comportement de routage ne peut pas être empêché via l'API de gestion des événements : utilisez plutôt l'attribut disabled
.
m(
m.route.Link,
{
href: '/foo',
selector: 'button.large',
disabled: true,
params: { key: 'value' },
options: { replace: true },
},
'link name'
);
// Rend comme :
// <button disabled aria-disabled="true" class="large">link name</button>
vnode = m(m.route.Link, attributes, children)
Argument | Type | Required | Description |
---|---|---|---|
attributes.href | Object | Yes | La route cible vers laquelle naviguer. |
attributes.disabled | Boolean | No | Désactive l'élément de manière accessible. |
attributes.selector | String|Object|Function | No | Un sélecteur pour m , la valeur par défaut est "a" . |
attributes.options | Object | No | Définit les options passées à m.route.set . |
attributes.params | Object | No | Définit les params passés à m.route.set . |
attributes | Object | No | Tout autre attribut à transmettre à m . |
children | Array<Vnode>|String|Number|Boolean | No | Les vnodes enfants associés à ce lien. |
returns | Vnode | Un vnode. |
m.route.param
Récupère un paramètre de la route à partir de la dernière route entièrement résolue. Un paramètre de route est une paire clé-valeur. Les paramètres de route peuvent provenir de plusieurs endroits :
- interpolations de route (par exemple, si une route est
/users/:id
et qu'elle se résout en/users/1
, le paramètre de route a une cléid
et une valeur"1"
) - chaînes de requête du routeur (par exemple, si le chemin est
/users?page=1
, le paramètre de route a une clépage
et une valeur"1"
) history.state
(par exemple, sihistory.state
est{foo: "bar"}
, le paramètre de route a la cléfoo
et la valeur"bar"
)
value = m.route.param(key)
Argument | Type | Required | Description |
---|---|---|---|
key | String | No | Un nom de paramètre de route (par exemple, id dans la route /users/:id , ou page dans le chemin /users/1?page=3 , ou une clé dans history.state ). |
returns | String|Object | Retourne une valeur pour la clé spécifiée. Si aucune clé n'est spécifiée, la méthode retourne un objet contenant toutes les clés d'interpolation. |
Notez que dans la fonction onmatch
d'un RouteResolver
, la nouvelle route n'a pas encore été entièrement résolue, et m.route.param()
retournera les paramètres de la route précédente, le cas échéant. onmatch
reçoit les paramètres de la nouvelle route comme argument.
m.route.SKIP
Une valeur spéciale qui peut être retournée par la fonction onmatch
d'un résolveur de route pour passer à la route suivante.
RouteResolver
Un RouteResolver
est un objet qui n'est pas un composant et qui contient une méthode onmatch
et/ou une méthode render
. Les deux méthodes sont facultatives, mais au moins une doit être présente.
Si un objet peut être détecté comme un composant (par la présence d'une méthode view
ou en étant une function
/class
), il sera traité comme tel même s'il a des méthodes onmatch
ou render
. Un RouteResolver
n'étant pas un composant, il ne possède pas de méthodes de cycle de vie.
En règle générale, les RouteResolvers
doivent se trouver dans le même fichier que l'appel m.route
, tandis que les définitions de composants doivent se trouver dans leurs propres modules.
routeResolver = {onmatch, render}
Lorsque vous utilisez des composants, vous pouvez les considérer comme une syntaxe simplifiée pour ce résolveur de route. En supposant que votre composant est Home
:
var routeResolver = {
onmatch: function () {
return Home;
},
render: function (vnode) {
return [vnode];
},
};
routeResolver.onmatch
Le hook onmatch
est appelé lorsque le routeur doit trouver un composant à rendre. Il est appelé une fois à chaque changement de chemin de routeur, mais pas lors des redessins ultérieurs sur le même chemin. Il peut être utilisé pour exécuter une logique avant l'initialisation d'un composant (par exemple, une logique d'authentification, un préchargement de données, un suivi d'analyse de redirection, etc.).
Cette méthode vous permet également de définir de manière asynchrone quel composant sera rendu, ce qui la rend appropriée pour la division du code et le chargement asynchrone de modules. Pour rendre un composant de manière asynchrone, retournez une promesse qui se résout en un composant.
Pour plus d'informations sur onmatch
, consultez la section résolution avancée des composants.
routeResolver.onmatch(args, requestedPath, route)
Argument | Type | Description |
---|---|---|
args | Object | Les paramètres de routage. |
requestedPath | String | Le chemin du routeur demandé par la dernière action de routage, y compris les valeurs des paramètres de routage interpolés, mais sans le préfixe. Lorsque onmatch est appelé, la résolution de ce chemin n'est pas encore terminée et m.route.get() retourne toujours le chemin précédent. |
route | String | Le chemin du routeur demandé par la dernière action de routage, sans les valeurs des paramètres de routage interpolés. |
returns | Component|\Promise<Component>|undefined | Retourne un composant ou une promesse qui se résout en un composant. |
Si onmatch
retourne un composant ou une promesse qui se résout en un composant, ce composant est utilisé comme vnode.tag
pour le premier argument de la méthode render
du RouteResolver
. Sinon, vnode.tag
est défini sur "div"
. De même, si la méthode onmatch
est omise, vnode.tag
est également "div"
.
Si onmatch
retourne une promesse qui est rejetée, le routeur redirige vers defaultRoute
. Vous pouvez remplacer ce comportement en appelant .catch
sur la chaîne de promesses avant de la retourner.
routeResolver.render
La méthode render
est appelée à chaque redessin pour une route correspondante. Elle est similaire à la méthode view
des composants et elle existe pour simplifier la composition de composants. Elle vous permet également d'échapper au comportement normal de Mithril.js qui consiste à remplacer l'ensemble du sous-arbre.
vnode = routeResolver.render(vnode)
Argument | Type | Description |
---|---|---|
vnode | Object | Un vnode dont l'objet d'attributs contient des paramètres de routage. Si onmatch ne retourne pas un composant ou une promesse qui se résout en un composant, le champ tag du vnode est par défaut "div" . |
vnode.attrs | Object | Une carte des valeurs des paramètres d'URL. |
returns | Array<Vnode>|Vnode | Les vnodes à rendre. |
Le paramètre vnode
est juste m(Component, m.route.param())
où Component
est le composant résolu pour la route (après routeResolver.onmatch
) et m.route.param()
est comme documenté ici. Si vous omettez cette méthode, la valeur de retour par défaut est [vnode]
, encapsulé dans un fragment afin que vous puissiez utiliser des paramètres de clé. Combiné avec un paramètre :key
, il devient un fragment clé à un seul élément, puisqu'il finit par rendre quelque chose comme [m(Component, {key: m.route.param("key"), ...})]
.
How it works
Le routage est un système permettant de créer des applications monopages (SPA), c'est-à-dire des applications qui peuvent passer d'une "page" à une autre sans nécessiter un rafraîchissement complet du navigateur.
Il permet une navigation fluide tout en préservant la possibilité de mettre en signet chaque page individuellement, et la possibilité de naviguer dans l'application via le mécanisme d'historique du navigateur.
Le routage sans rafraîchissement de page est rendu partiellement possible par l'API history.pushState
. En utilisant cette API, il est possible de modifier par programmation l'URL affichée par le navigateur après le chargement d'une page, mais il est de la responsabilité du développeur de l'application de s'assurer que la navigation vers une URL donnée à partir d'un état froid (par exemple, un nouvel onglet) affichera le balisage approprié.
Routing strategies
La stratégie de routage détermine la manière dont une bibliothèque implémente le routage. Il existe trois stratégies générales qui peuvent être utilisées pour implémenter un système de routage SPA, et chacune a des mises en garde différentes :
m.route.prefix = '#!'
(par défaut) – Utilisation de la partie identificateur de fragment (aka le hachage) de l'URL. Une URL utilisant cette stratégie ressemble généralement àhttps://localhost/#!/page1
.m.route.prefix = '?'
– Utilisation de la chaîne de requête. Une URL utilisant cette stratégie ressemble généralement àhttps://localhost/?/page1
.m.route.prefix = ''
– Utilisation du nom de chemin. Une URL utilisant cette stratégie ressemble généralement àhttps://localhost/page1
.
L'utilisation de la stratégie de hachage est garantie de fonctionner dans les navigateurs qui ne prennent pas en charge history.pushState
, car elle peut revenir à l'utilisation de onhashchange
. Utilisez cette stratégie si vous souhaitez conserver les hachages uniquement en local.
La stratégie de chaîne de requête permet la détection côté serveur, mais elle n'apparaît pas comme un chemin normal. Utilisez cette stratégie si vous voulez prendre en charge et potentiellement détecter les liens ancrés côté serveur et que vous n'êtes pas en mesure d'apporter les modifications nécessaires pour prendre en charge la stratégie de nom de chemin (comme si vous utilisez Apache et que vous ne pouvez pas modifier votre .htaccess
).
La stratégie de nom de chemin produit les URL les plus propres, mais nécessite la configuration du serveur pour servir le code de l'application monopage à partir de chaque URL vers laquelle l'application peut router. Utilisez cette stratégie si vous voulez des URL plus propres.
Les applications monopages qui utilisent la stratégie de hachage utilisent souvent la convention d'avoir un point d'exclamation après le hachage pour indiquer qu'elles utilisent le hachage comme mécanisme de routage et non à des fins de liaison à des ancres. La chaîne #!
est connue sous le nom de hashbang.
La stratégie par défaut utilise le hashbang.
Typical usage
En général, il est nécessaire de créer quelques composants pour associer les routes :
var Home = {
view: function () {
return [m(Menu), m('h1', 'Accueil')];
},
};
var Page1 = {
view: function () {
return [m(Menu), m('h1', 'Page 1')];
},
};
Dans l'exemple ci-dessus, il existe deux composants : Home
et Page1
. Chacun contient un menu et du texte. Le menu est lui-même défini comme un composant pour éviter la répétition :
var Menu = {
view: function () {
return m('nav', [
m(m.route.Link, { href: '/' }, 'Accueil'),
m(m.route.Link, { href: '/page1' }, 'Page 1'),
]);
},
};
Nous pouvons maintenant définir des routes et mapper nos composants à celles-ci :
m.route(document.body, '/', {
'/': Home,
'/page1': Page1,
});
Ici, nous spécifions deux routes : /
et /page1
, qui rendent leurs composants respectifs lorsque l'utilisateur navigue vers chaque URL.
Navigating to different routes
Dans l'exemple ci-dessus, le composant Menu
a deux m.route.Link
s. Cela crée un élément, par défaut un <a>
, et le configure de manière à ce que, lorsque l'utilisateur clique dessus, il navigue vers une autre route. Il ne navigue pas à distance, seulement localement.
Vous pouvez également naviguer par programmation, via m.route.set(route)
. Par exemple, m.route.set("/page1")
.
Lors de la navigation entre les routes, le préfixe du routeur est géré pour vous. En d'autres termes, omettez le hashbang #!
(ou tout autre préfixe que vous avez défini pour m.route.prefix
) lors de la liaison des routes Mithril.js, y compris dans m.route.set
et dans m.route.Link
.
Notez que lors de la navigation entre les composants, l'ensemble du sous-arbre est remplacé. Utilisez un résolveur de route avec une méthode render
si vous voulez simplement patcher le sous-arbre.
Routing parameters
Parfois, nous voulons qu'un identifiant variable ou des données similaires apparaissent dans une route, mais nous ne voulons pas spécifier explicitement une route distincte pour chaque identifiant possible. Pour ce faire, Mithril.js gère les routes paramétrées :
var Edit = {
view: function (vnode) {
return [m(Menu), m('h1', 'Modification de ' + vnode.attrs.id)];
},
};
m.route(document.body, '/edit/1', {
'/edit/:id': Edit,
});
Dans l'exemple ci-dessus, nous avons défini une route /edit/:id
. Cela crée une route dynamique qui correspond à toute URL qui commence par /edit/
et est suivie de données (par exemple, /edit/1
, edit/234
, etc.). La valeur id
est ensuite mappée comme un attribut du vnode du composant (vnode.attrs.id
).
Il est possible d'avoir plusieurs arguments dans une route, par exemple /edit/:projectID/:userID
produirait les propriétés projectID
et userID
sur l'objet d'attributs vnode du composant.
Key parameter
Lorsqu'un utilisateur navigue d'une route paramétrée vers la même route avec un paramètre différent (par exemple, passer de /page/1
à /page/2
étant donné une route /page/:id
), le composant ne serait pas recréé à partir de zéro puisque les deux routes se résolvent au même composant, et donc entraîneraient une différence virtuelle de DOM en place. Cela a pour effet secondaire de déclencher le hook onupdate
, plutôt que oninit
/oncreate
. Cependant, il est fréquent qu'un développeur souhaite synchroniser la recréation du composant avec l'événement de changement de route.
Pour y parvenir, il est possible de combiner le paramétrage des routes avec des clés pour une solution très pratique :
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
Cela signifie que le vnode qui est créé pour le composant racine de la route a un objet de paramètre de route key
. Les paramètres de route deviennent des attrs
dans le vnode. Ainsi, lors du passage d'une page à une autre, la key
change et provoque la recréation du composant à partir de zéro (puisque la clé indique au moteur de DOM virtuel que les anciens et les nouveaux composants sont des entités différentes).
Vous pouvez pousser cette idée plus loin pour créer des composants qui se recréent lors du rechargement :
m.route.set(m.route.get(), {key: Date.now()})
Ou même utiliser la fonctionnalité history state
pour réaliser des composants rechargeables sans polluer l'URL :
m.route.set(m.route.get(), null, {state: {key: Date.now()}})
Notez que le paramètre key
ne fonctionne que pour les routes de composants. Si vous utilisez un résolveur de route, vous devrez utiliser un fragment clé à un seul enfant, en passant key: m.route.param("key")
, pour accomplir la même chose.
Variadic routes
Il est également possible d'avoir des routes variadiques, c'est-à-dire une route avec un argument qui contient des noms de chemin d'URL qui contiennent des barres obliques :
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': Edit,
});
Handling 404s
Pour une application JavaScript isomorphe / universelle, un paramètre d'URL et une route variadique combinés sont très utiles pour afficher une page d'erreur 404 personnalisée.
Dans le cas d'une erreur 404 Not Found, le serveur renvoie la page personnalisée au client. Lorsque Mithril.js est chargé, il redirigera le client vers la route par défaut car il ne peut pas connaître cette route.
m.route(document.body, '/', {
'/': homeComponent,
// [...]
'/:404...': errorPageComponent,
});
History state
Il est possible de tirer pleinement parti de l'API history.pushState
sous-jacente pour améliorer l'expérience de navigation de l'utilisateur. Par exemple, une application pourrait "se souvenir" de l'état d'un grand formulaire lorsque l'utilisateur quitte une page en s'éloignant, de sorte que si l'utilisateur appuyait sur le bouton de retour du navigateur, il aurait le formulaire rempli plutôt qu'un formulaire vierge.
Par exemple, vous pourriez créer un formulaire comme celui-ci :
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 }, 'Rechercher'),
]);
},
};
m.route(document.body, '/', {
'/': Form,
});
De cette façon, si l'utilisateur effectue une recherche et appuie sur le bouton de retour pour revenir à l'application, l'entrée sera toujours remplie avec le terme de recherche. Cette technique peut améliorer l'expérience utilisateur des grands formulaires et d'autres applications où un état temporaire est laborieux à produire pour un utilisateur.
Changing router prefix
Le préfixe du routeur est un fragment de l'URL qui détermine la stratégie sous-jacente utilisée par le routeur.
// 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';
Résolution avancée des composants
Au lieu d'associer un composant directement à une route, vous pouvez spécifier un objet RouteResolver
. Un objet RouteResolver
contient une méthode onmatch()
et/ou render()
. Les deux méthodes sont facultatives, mais au moins l'une d'entre elles doit être présente.
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath, route) {
return Home;
},
render: function (vnode) {
return vnode; // équivalent à m(Home)
},
},
});
Les RouteResolver
sont utiles pour implémenter divers cas d'utilisation avancés du routage.
Envelopper un composant avec une mise en page
Il est souvent souhaitable d'encapsuler tout ou partie des composants routés dans une structure réutilisable (souvent appelée "mise en page"). Pour ce faire, vous devez d'abord créer un composant qui contient le balisage commun qui enveloppera les différents composants :
var Layout = {
view: function (vnode) {
return m('.layout', vnode.children);
},
};
Dans l'exemple ci-dessus, la mise en page se compose simplement d'un <div class="layout">
qui contient les enfants passés au composant. Dans un scénario réel, elle pourrait être aussi complexe que nécessaire.
Une façon d'appliquer cette mise en page est de définir un composant anonyme directement dans la configuration des routes :
// exemple 1
m.route(document.body, '/', {
'/': {
view: function () {
return m(Layout, m(Home));
},
},
'/form': {
view: function () {
return m(Layout, m(Form));
},
},
});
Cependant, il est important de noter que, comme le composant de niveau supérieur est anonyme, le passage de la route /
à la route /form
(ou vice versa) détruira le composant anonyme et recréera le DOM à partir de zéro. Si le composant Layout
avait des méthodes de cycle de vie définies, les hooks oninit
et oncreate
seraient appelés à chaque changement de route. Selon l'application, cela peut être souhaitable ou non.
Si vous préférez que le composant Layout
soit comparé et maintenu intact plutôt que recréé à partir de zéro, vous devez utiliser un RouteResolver
comme objet racine :
// exemple 2
m.route(document.body, '/', {
'/': {
render: function () {
return m(Layout, m(Home));
},
},
'/form': {
render: function () {
return m(Layout, m(Form));
},
},
});
Dans ce cas, si le composant Layout
a des méthodes de cycle de vie oninit
et oncreate
, elles ne se déclencheront qu'au premier changement de route (en supposant que toutes les routes utilisent la même mise en page).
Pour clarifier la différence entre les deux exemples, l'exemple 1 est fonctionnellement équivalent à ce code :
// fonctionnellement équivalent à l'exemple 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);
},
},
});
Étant donné que Anon1
et Anon2
sont des composants différents, leurs sous-arbres (y compris Layout
) sont recréés à partir de zéro. C'est également ce qui se passe lorsque les composants sont utilisés directement sans RouteResolver
.
Dans l'exemple 2, étant donné que Layout
est le composant de niveau supérieur dans les deux routes, le DOM du composant Layout
est comparé (c'est-à-dire laissé intact s'il n'a pas de modifications), et seul le changement de Home
à Form
déclenche une recréation de cette sous-section du DOM.
Redirection
Le hook onmatch
du RouteResolver
peut être utilisé pour exécuter une logique avant l'initialisation du composant de niveau supérieur d'une route. Vous pouvez utiliser soit m.route.set()
de Mithril, soit l'API history
native de HTML. Avec l'API history
, le hook onmatch
doit renvoyer une Promise
qui ne se résout jamais afin d'empêcher la résolution de la route correspondante. m.route.set()
annule la résolution de la route correspondante en interne, ce qui n'est donc pas nécessaire avec elle.
Exemple : authentification
L'exemple ci-dessous montre comment implémenter un contrôle d'accès qui empêche les utilisateurs de voir la page /secret
à moins qu'ils ne soient connectés.
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,
});
Lorsque l'application se charge, onmatch
est appelé et, comme isLoggedIn
est faux, l'application redirige vers /login
. Une fois que l'utilisateur a cliqué sur le bouton de connexion, isLoggedIn
est défini sur true
, et l'application redirige vers /secret
. Le hook onmatch
s'exécute une fois de plus, et comme isLoggedIn
est vrai cette fois, l'application affiche le composant Home
.
Pour simplifier, dans l'exemple ci-dessus, l'état de connexion est stocké dans une variable globale, et cet indicateur est simplement basculé lorsque l'utilisateur clique sur le bouton de connexion. Dans une application réelle, un utilisateur devrait évidemment fournir des informations d'identification de connexion appropriées, et cliquer sur le bouton de connexion déclencherait une requête vers un serveur pour authentifier l'utilisateur :
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,
});
Préchargement des données
En général, un composant peut charger des données lors de son initialisation. Le chargement des données de cette manière entraîne deux rendus du composant. Le premier rendu a lieu lors du routage, et le second se déclenche une fois la requête terminée. Il est important de noter que loadUsers()
renvoie une Promise
, mais toute Promise
renvoyée par oninit
est actuellement ignorée. Le deuxième rendu provient de l'option background
pour 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);
})
: 'chargement';
},
},
});
Dans l'exemple ci-dessus, lors du premier rendu, l'interface utilisateur affiche "chargement"
car state.users
est un tableau vide avant la fin de la requête. Ensuite, une fois les données disponibles, l'interface est mise à jour et une liste d'ID d'utilisateur est affichée.
Les RouteResolver
peuvent être utilisés comme un mécanisme pour précharger les données avant de rendre un composant afin d'éviter le clignotement de l'interface utilisateur et ainsi contourner le besoin d'un indicateur de chargement :
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);
});
},
},
});
Ici, render
ne s'exécute qu'une fois la requête terminée, ce qui rend l'opérateur ternaire inutile.
Découpage du code
Dans une grande application, il peut être souhaitable de télécharger le code pour chaque route à la demande, au lieu de tout télécharger au départ. La division de la base de code de cette manière est connue sous le nom de découpage du code (ou chargement paresseux). Dans Mithril.js, cela peut être réalisé en renvoyant une promesse depuis le hook onmatch
:
Dans sa version la plus simple, on pourrait faire ce qui suit :
// 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');
},
},
});
Cependant, en pratique, pour que cela fonctionne en production, il serait nécessaire de regrouper toutes les dépendances du module Home.js
dans le fichier qui est finalement servi par le serveur.
Heureusement, il existe un certain nombre d'outils qui facilitent la tâche de regrouper les modules pour le chargement paresseux. Exemple avec import(...)
dynamique natif, pris en charge par de nombreux bundlers :
m.route(document.body, '/', {
'/': {
onmatch: function () {
return import('./Home.js');
},
},
});
Routes typées
Dans certains cas de routage avancés, vous pouvez vouloir contraindre une valeur au-delà du simple chemin, en ne faisant correspondre que quelque chose comme un ID numérique. Vous pouvez le faire assez facilement en renvoyant m.route.SKIP
depuis une route.
m.route(document.body, '/', {
'/view/:id': {
onmatch: function (args) {
if (!/^\d+$/.test(args.id)) return m.route.SKIP;
return ItemView;
},
},
'/view/:name': UserView,
});
Routes cachées
Il peut arriver que vous souhaitiez masquer certaines routes pour certains utilisateurs, mais pas à d'autres. Par exemple, un utilisateur peut être interdit de consulter un utilisateur particulier, et au lieu d'afficher une erreur d'autorisation, vous préféreriez simuler son absence et rediriger vers une vue 404 à la place. Dans ce cas, vous pouvez utiliser m.route.SKIP
pour simplement faire comme si la route n'existait pas.
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,
});
Annulation / blocage de route
Le RouteResolver
onmatch
peut empêcher la résolution en renvoyant une promesse qui ne se résout jamais. Cela peut être utilisé pour détecter les tentatives de résolutions de route redondantes et les annuler :
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath) {
if (m.route.get() === requestedPath) return new Promise(function () {});
},
},
});
Intégration tierce
Dans certaines situations, vous pouvez avoir besoin d'intégrer un autre framework comme React. Procédez comme suit :
- Définissez toutes vos routes en utilisant
m.route
comme d'habitude, mais assurez-vous de ne l'utiliser qu'une seule fois. Les points de routage multiples ne sont pas pris en charge. - Lorsque vous devez supprimer les abonnements aux routes, utilisez
m.mount(root, null)
, en utilisant la même racine que celle que vous avez utilisée avecm.route(root, ...)
.m.route
utilisem.mount
en interne pour tout connecter, ce n'est donc pas un processus magique.
Voici un exemple avec 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} />;
}
}
Et voici l'équivalent approximatif avec 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);
},
});