DEV Community

Cover image for Routage avec RiotJS
Steeve
Steeve

Posted on

Routage avec RiotJS

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 :

Riot application changing route on Firefox

É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>
Enter fullscreen mode Exit fullscreen mode

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 :

  1. 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.
  2. 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>
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

Route Avancée

Explorons le routage avancé avec les exigences suivantes pour un front-end :

  1. Afficher une page 404 si un chemin d'URL n'existe pas.
  2. Accéder aux paramètres de requête dans chaque composant de page.
  3. Pour chaque route, afficher un composant Riot en tant que page.
  4. Créer un fichier de configuration de routage définissant toutes les routes, chemins et composants.

Riot application on Google Chrome with a routing showing 404 when the page does not exist

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Ou le composant pages/p-not-found.riot ressemble à ceci :

<p-not-found>
    <h2> 404 Page Not Found </h2>
</p-not-found>
Enter fullscreen mode Exit fullscreen mode

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'
    }
  ]
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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'attribut is :
<route each={ page in state.pages  } path={ page.path }>
   <span is={ page.component } route={ route }></span>
</route>
Enter fullscreen mode Exit fullscreen mode
  • 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> 
Enter fullscreen mode Exit fullscreen mode
  • 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 le onMounted () {} 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()
}
Enter fullscreen mode Exit fullscreen mode
  • Pour chaque composant, la route actuelle est passée en tant que Props:
<span is={ r.component } route={ route }></span>
Enter fullscreen mode Exit fullscreen mode
  • 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>
Enter fullscreen mode Exit fullscreen mode

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)