SolidJS 2.0 est async first : qu'est ce que ça signifie ?

SolidJS 2.0 is Async First

La première version beta de SolidJS 2.0 est sortie la semaine dernière. Ryan Carniato a fait un stream pour expliquer en quoi on passait d'une version sync first à une version async first. Il a notamment présenté plusieurs exemples qui démontrent la simplicité de l'utilisation des expressions asynchrones dans SolidJS 2.0. On va voir ici un cas d'utilisation possible sur la récupération d'une liste paginée.

Spécificités de l'asynchronisme

Quand on récupère une donnée de manière synchrone, on n'a pas de question à se poser, la donnée est disponible et on l'utilise. Pour une donnée asynchrone par contre il y a plusieurs problèmes :

Il y a également une problématique de cohérence, si un résultat d'une opération asynchrone dépend d'une donnée synchrone (liée à une saisie par l'utilisateur par exemple) on veut en général n'afficher la nouvelle valeur de la donnée synchrone que lorsque le calcul de la donnée asynchrone est terminé pour avoir une incohérence entre les deux.

Comment cela est géré jusqu'à présent

Jusqu'à présent, le fonctionnement spécifique de l'asynchronisme faisait que l'on avait un traitement différent entre la donnée synchrone et la donnée asynchrone. On a vu par exemple émerger Tanstack Query pour cela. L'arrivée du hook use en React a également permis de simplifier la gestion des données asynchrones.

Au niveau de SolidJS 1.0 on a createSignal pour les données synchrones et createResource pour les données asynchrones.

La gestion des données asynchrones se simplifie au cours du temps grâce à de nouvelles primitives mais on a quand même un code spécifique, c'est en cela que l'on n'est pas async first.

Le changement de paradigme

L'idée avec Solid 2.0 c'est d'avoir un paradigme qui traite les données synchrones et asynchrones de la même manière. L'idée derrière cela c'est de se dire que si j'ai du code qui fonctionne avec des données synchrones, le même code fonctionne aussi avec des données asynchrones.

Dans un des exemple de Ryan on a un code de ce type

function App() {
  const [value, setValue] = createSignal(1);
  const increment = () => setValue((prev) => prev + 1);
  const result = createMemo(() => compute(value()));

  return (
    <div>
      <button type="button" onClick={increment}>{latest(value)}</button>
      <p class={[{ pending: isPending(result) }]}>Result for {value()} = {result().toFixed(3)}</p>
    </div>
  );
}

Ce code fonctionne de la même manière que la fonction compute soit une fonction synchrone ou asynchrone. Dans les deux cas, la fonction createMemo va retourner un type Accessor<number>.

Un point important à noter est que la valeur value ne sera mise à jour que lorsque la propriété dérivée result aura pu être calculée et donc quand compute aura retourné un résultat. L'idée c'est d'avoir une consistance entre toutes les données pour éviter d'afficher un résultat qui ne correspond pas à la valeur courante.

Pour traiter les besoins spécifiques à l'asynchrone Solid va fournir plusieurs éléments

Cas d'usage : liste paginée

A partir de ce nouveau mode de fonctionnement on peut très facilement implémenter une liste paginée avec le code suivant

const App = () => {
  const [page, setPage] = createSignal(1);
  const result = createMemo(() => getResults(page()));

  return (
    <div>
      <h1>Demo</h1>
      <button disabled={latest(page) === 1} onClick={() => setPage(prev => prev - 1)}>Previous</button>
      Page {latest(page)}
      <button onClick={() => setPage(prev => prev + 1)}>Next</button>
      <h2>Result </h2>
        <Loading fallback="Loading...">
          <div class={[{loading: isPending(result)}]}>
            <For each={result()}>
              {(value) => <p>{value()}</p>}
            </For>
            <p>{result().length} items</p>
          </div>
        </Loading>
    </div>
  );
};

On a automatiquement un loader au premier chargement de la liste puis la classe loading qui sera appliqué à chaque changement de page.

Conclusion

Cet exemple simple présente les apports du nouveau paradigme “Async First” de SolidJS 2.0. On pourra voir par la suite que cela amène d'autres avantages, notamment la possibilité d'initialiser les signaux avec une fonction réactive.

Par rapport à Solid 1.0, on simplifie l'API du framework puisque l'on utilise systématiquement createSignal plutôt que d'utiliser une autre API createResource pour les données asynchrones.

L'intégration de Solid 2.0 avec SolidStart n'est pas encore disponible mais cela veut également dire que createAsync va disparaître et que l'on pourra utiliser createMemo pour appeler une server function.