Cet article traite de la création d'une application Riot couplée avec Riot-Route, la solution officielle de routage côté client de Riot.
Avant de commencer, assurez-vous d'avoir une application de base RiotJS, ou lisez mes articles précédents.
Le routage côté client lie l'URL du navigateur au contenu de la page. Lorsqu'un utilisateur navigue dans l'application Riot, l'URL change sans demander un nouveau front-end à un serveur. Ceci est appelé une SPA, pour Single Page Applications : Riot gère toutes les mises à jour de données et la navigation sans recharger la page, ce qui rend l'application riche et réactive !
Créons l'exemple de routage le plus simple ; ensuite, nous explorerons un usage avancé en production.
Route de base
Nous visons à créer l'application suivante : un Menu à gauche affichant des liens vers différentes pages, et lorsqu'on clic sur un lien, la section de droite affiche la page correspondante. Le style est alimenté par le CSS Material Design BeerCSS :
Écrivez le code suivant dans ./index.riot. Le HTML provient de la documentation BeerCSS, et j'ai ajouté la syntaxe RiotJS pour la logique :
<index-riot>
<router>
<nav class="drawer left right-round border">
<header>
<nav>
<img class="circle" src="./examples/data/img-card.png"/>
<h6>Jon Snow</h6>
</nav>
</header>
<!-- These links will trigger automatically HTML5 history events -->
<a href="/">
<i>inbox</i>
<span class="max">Inbox</span>
<b>24</b>
</a>
<a href="/favorite">
<i>favorite</i>
<span class="max">Starred</span>
<b>3</b>
</a>
<a href="/sent">
<i>send</i>
<span class="max">Sent</span>
<b>11</b>
</a>
<div class="medium-divider"></div>
<a href="/subscription">
<i>rocket</i>
<span>Subscription</span>
</a>
<a href="/settings">
<i>settings</i>
<span>Settings</span>
</a>
</nav>
<!-- Your application routes will be rendered here -->
<span style="display:block;margin-left:20px;margin-top:20px">
<route path="/"> <h2>Inbox</h2> </route>
<route path="/favorite"> <h2>Starred</h2> </route>
<route path="/sent"> <h2>Sent</h2> </route>
<route path="/subscription"> <h2>Subscription</h2> </route>
<route path="/settings"> <h2>Settings</h2> </route>
</span>
</router>
<script>
import { Router, Route } from '@riotjs/route'
export default {
components: { Router, Route }
}
</script>
</index-riot>
Code Source: https://github.com/steevepay/riot-beercss/blob/main/examples/riot-route/index.basic.riot
Cet exemple utilise deux composants fournis par riot-route :
-
Router : Le
<router>
enveloppe l'application Riot et détecte automatiquement tous les clics sur les liens qui doivent déclencher un changement de navigation. -
Route : Le
<route path="/some/route/:params">
rend le contenu de la page si l'attribut path correspond au chemin d'URL actuel. Le path peut accepter des expressions régulières (Regex) ou des paramètres, et vous pouvez accéder à la route actuelle avec l'objet route :
<route path="/:some/:route/:param"> {JSON.stringify(route.params)} </route>
<route path="/search(.*)">
<!-- Assuming the URL is "/search?q=awesome" -->
{route.searchParams.get('q')}
</route>
Code Source trouvé sur Riot-Route
Pour accéder à la route actuelle dans la section JavaScript, il est possible d'importer l'objet route depuis '@riotjs/route' :
import { Router, Route, route } from '@riotjs/route'
Route Avancée
Explorons le routage avancé avec les exigences suivantes pour un front-end :
- Afficher une page 404 si un chemin d'URL n'existe pas.
- Accéder aux paramètres de requête dans chaque composant de page.
- Pour chaque route, afficher un composant Riot en tant que page.
- Créer un fichier de configuration de routage définissant toutes les routes, chemins et composants.
Dans un premier temps, nous allons créer 6 composants, un pour chaque page et un autre pour la page 404 Not Found. Les composants sont situés dans le répertoire pages :
pages/p-favorite.riot
pages/p-inbox.riot
pages/p-sent.riot
pages/p-settings.riot
pages/p-subscription.riot
pages/p-not-found.riot
Chaque composant possède une seule balise de titre <h2>
, par exemple, le composant pages/p-sent.riot ressemble à ceci :
<p-sent>
<h2>Sent</h2>
</p-sent>
Ou le composant pages/p-not-found.riot ressemble à ceci :
<p-not-found>
<h2> 404 Page Not Found </h2>
</p-not-found>
Ensuite, créez un fichier de configuration de routage global dans routes.js. Le fichier renvoie une liste de pages, et chaque page possède un nom (name), un chemin avec une expression régulière longue (path), et un composant correspondant (component):
export default [
{
name : 'Inbox',
href : '/',
path : '/(/?[?#].*)?(#.*)?',
component: 'p-inbox',
icon : 'inbox'
},
{
name : 'Starred',
href : '/favorite',
path : '/favorite(/?[?#].*)?(#.*)?',
component: 'p-favorite',
icon : 'favorite'
},
{
name : 'Sent',
href : '/sent',
path : '/sent(/?[?#].*)?(#.*)?',
component: 'p-sent',
icon : 'send',
separator: true
},
{
name : 'Subscription',
href : '/subscription',
path : '/subscription(/?[?#].*)?(#.*)?',
component: 'p-subscription',
icon : 'rocket',
},
{
name : 'Settings',
href : '/settings',
path : '/settings(/?[?#].*)?(#.*)?',
component: 'p-settings',
icon : 'settings'
}
]
Source code: https://github.com/steevepay/riot-beercss/blob/main/examples/riot-route/routes.js
Le composant <route>
de Riot utilisera l'attribut path
. Chaque expression régulière est composée de 3 parties :
-
/settings
: Chemin de la page (chaîne statique requise) -
(/?[?#].*)
: Paramètres de requête (groupe facultatif) -
(#.*)?
: Fragment, une section au sein d'une page (groupe facultatif)
Maintenant, importez routes.js et tous les composants dans le fichier index.riot : Définissez les composants dans l'objet Riot components:{}
, et chargez les routes dans l'objet state:{}
:
<index-riot>
<router>
<nav class="drawer left right-round border">
<header>
<nav>
<img class="circle" src="./examples/data/img-card.png"/>
<h6>Jon Snow</h6>
</nav>
</header>
<!-- Navigation bar created dynamically -->
<template each={ page in state.pages }>
<a href={ page.href }>
<i>{ page.icon }</i>
<span class="max">{ page.name }</span>
</a>
<div if={ page.separator === true } class="medium-divider"></div>
</template>
</nav>
<!-- Your application components/routes will be rendered here -->
<span style="display:block;margin-left:20px;margin-top:20px">
<route each={ page in state.pages } path={ page.path }>
<span is={ page.component } route={ route }></span>
</route>
<p-not-found if={ state.showNotFound } />
</span>
</router>
<script>
import { Router, Route, route, toRegexp, match } from '@riotjs/route';
import pages from './routes.js'
import pInbox from "./pages/p-inbox.riot";
import pFavorite from "./pages/p-favorite.riot";
import pSent from "./pages/p-sent.riot"
import pSettings from "./pages/p-settings.riot"
import pSubscription from "./pages/p-subscription.riot"
import pNotFound from "./pages/p-not-found.riot"
export default {
components: { Router, Route, pInbox, pFavorite, pSent, pSettings, pSubscription, pNotFound },
state: {
pages,
showNotFound: false
},
onMounted (props, state) {
// ROUTING: create a stream on all routes
this.anyRouteStream = route('(.*)')
// ROUTING: check any route change to understand if the not found site should be displayed
this.anyRouteStream.on.value((path) => {
this.update({ showNotFound: !this.state.pages.some(p => match(path.pathname, toRegexp(p?.path))) })
})
},
onUnmounted() {
this.anyRouteStream.end()
}
}
</script>
</index-riot>
Code Source: https://github.com/steevepay/riot-beercss/blob/main/examples/riot-route/index.advanced.riot
Ce code diffère beaucoup de l'exemple de base ; voici les principaux changements :
- Pour imprimer un composant pour chaque page, une boucle est créée sur
state.pages
. Au sein de chaque<route></route>
, les éléments HTML span sont utilisés comme composants Riot en ajoutant l'attributis
:
<route each={ page in state.pages } path={ page.path }>
<span is={ page.component } route={ route }></span>
</route>
- Les liens du menu sont également générés grâce à la configuration de route, accessible avec
state.pages
:
<template each={ page in state.pages }>
<a href={ page.href }>
<i>{ page.icon }</i>
<span class="max">{ page.name }</span>
</a>
<div if={ page.separator === true } class="medium-divider"></div>
</template>
- L'objet
route
est utilisé dans la partie JavaScript pour vérifier si l'URL actuelle existe : Un Stream de routes est créé pour écouter les changements de route dans leonMounted () {}
hook (cycle de vie Riot). Lorsqu'une route change, une fonction vérifie si l'URL correspond à une route existante :
onMounted (props, state) {
this.anyRouteStream = route('(.*)')
this.anyRouteStream.on.value((path) => {
this.update({ showNotFound: !this.state.pages.some(p => match(path.pathname, toRegexp(p?.path))) })
})
},
onUnmounted() {
// When the component is unmounted, the stream is stopped.
this.anyRouteStream.end()
}
- Pour chaque composant, la route actuelle est passée en tant que Props:
<span is={ r.component } route={ route }></span>
- Au sein de chaque composant, la route est accessible avec
props.route
, par exemple dans le fichier c-inbox.riot :
<p-inbox>
<h2> Inbox </h2>
<span>Filter: { props.route.searchParams.get('filter') }</span><br>
<span>Order By: { props.route.searchParams.get('order') }</span>
</p-inbox>
Vous pouvez maintenant utiliser les paramètres de requête pour demander une API lorsque la page est chargée dans le onMounted(){}
hook (cycle de vie Riot).
Conclusion
Riot Route vous permet de créer la navigation facilement, avec une syntaxe toujours proche des standards HTML.
Une limitation des applications monopages est qu'elles dépendent d'un backend/API pour charger les données. Le navigateur/utilisateur doit attendre que le contenu soit rendu. Pour contrer ce problème, vous pouvez rendre le HTML côté serveur avec Riot-SSR : la requête reçoit la page directement remplie de données.
Passez une excellente journée ! Santé 🍻
Top comments (0)