Gestion d’état dans Astro : Une plongée approfondie dans Nanostores

Bienvenue dans la première partie de la série « Nanostores in Astro: A Multi-Framework Adventure ». Si vous avez du mal à gérer l’état dans vos projets Astro utilisant plusieurs frameworks, vous êtes au bon endroit. Aujourd’hui, nous explorerons Nanostores, une solution de gestion d’état légère qui s’intègre parfaitement à l’architecture d’îles de composants d’Astro.

Dans cet article, nous approfondirons comment Nanostores peut simplifier la gestion de l’état entre Astro, React, Vue et Svelte. Nous couvrirons l’état indépendant, l’état partagé et présenteraons la gestion de l’état persistante. Commencez donc cette aventure pour simplifier la gestion de l’état dans vos projets Astro.

Si vous êtes prêt à plonger directement dans le sujet, voici un bref résumé et des liens essentiels :

N’hésitez pas à explorer la démonstration et le code à côté de cet article pour une expérience d’apprentissage pratique !

Nanostores est une bibliothèque de gestion d’État minimaliste conçue avec l’agnosticisme du framework en tête. Elle fournit une API directe pour créer et gérer des morceaux atomiques d’État, qui peuvent être facilement partagés et mis à jour dans différentes parties de votre application.

  1. Lightweight et rapide : Nanostores est extrêmement petit (entre 265 et 814 octets), ce qui garantit qu’il ne vous augmentera pas la taille de votre bundle.

  2. Agnosticisme du framework : Parfait pour l’écosystème multi-framework d’Astro. Il s’intègre parfaitement avec React, Vue, Svelte, Solid et JavaScript vanilla.

  3. API simple : Aucun setup complexe ou boilerplate. Elle est directe et intuitive à utiliser.

  4. Compatible avec les Îles de composants d’Astro : Nanostores améliore l’architecture d’îles de composants d’Astro, permettant une gestion efficace de l’État entre composants interactifs isolés.

Nanostores se base sur trois concepts principaux :

  1. Atomes: Des petits magasins qui contiennent une seule valeur.

  2. Cartes: Des magasins qui contiennent des objets avec plusieurs propriétés.

  3. Magasins Calculés: Des magasins dérivés qui calculent leur valeur en fonction d’autres magasins.

Examinons un exemple rapide :

import { atom, map, computed } from 'nanostores'

// Atom
const count = atom(0)

// Map
const user = map({ name: 'Astro Fan', isLoggedIn: false })

// Computed Store
const greeting = computed([user], (user) => 
  user.isLoggedIn ? `Welcome back, ${user.name}!` : 'Hello, guest!'
)

Dans ce extrait, nous avons créé un atome pour un compteur, une carte pour les données utilisateur et un magasin calculé pour un salutation dynamique. Simple, n’est-ce pas ?

Commencer avec Nanostores dans votre projet Astro est simple. Voici comment le faire :

  1. Premièrement, installez Nanostores et ses intégrations avec le framework :
# Using npm
npm install nanostores
npm install @nanostores/react  # For React
npm install @nanostores/vue    # For Vue

# Using yarn
yarn add nanostores
yarn add @nanostores/react     # For React
yarn add @nanostores/vue       # For Vue

# Using pnpm
pnpm add nanostores
pnpm add @nanostores/react     # For React
pnpm add @nanostores/vue       # For Vue

# Note: Svelte doesn't require a separate integration
  1. Créez un nouveau fichier pour vos magasins, disons src/stores/counterStore.js :
import { atom } from 'nanostores'

export const count = atom(0)

export function increment() {
  count.set(count.get() + 1)
}

export function decrement() {
  count.set(count.get() - 1)
}
  1. Maintenant, vous pouvez utiliser ce magasin dans n’importe lequel de vos composants. Voici un exemple rapide dans un composant Astro :
---
import { count, increment, decrement } from '../stores/counterStore'
---

<div>
  <button onclick={decrement}>-</button>
  <span>{count.get()}</span>
  <button onclick={increment}>+</button>
</div>

<script>
  import { count } from '../stores/counterStore'

  count.subscribe(value => {
    document.querySelector('span').textContent = value
  })
</script>

Et là, vous en avez ! Vous venez de configurer un Nanostore dans votre projet Astro.

Dans les projets Astro multi-frameworks, vous pourriez souhaiter gérer l’état indépendamment à l’intérieur des composants de différentes frameworks. Nanostores permet de réaliser cela de manière transparente. Explorons comment implémenter la gestion de l’état indépendant à travers les composants React, Vue, Svelte et Astro.

Nous implémenterons un simple compteur dans chacun des frameworks pour illustrer la gestion indépendante de l’état.

Tout d’abord, créez-nous notre propre magasin de compteur indépendant :

// src/stores/independentCounterStore.js
import { atom } from 'nanostores'

export const reactCount = atom(0)
export const vueCount = atom(0)
export const svelteCount = atom(0)
export const astroCount = atom(0)

export function increment(store) {
  store.set(store.get() + 1)
}

export function decrement(store) {
  store.set(store.get() - 1)
}

Maintenant, implémentons ce compteur dans chacun des frameworks :

// src/components/ReactCounter.jsx
import { useStore } from '@nanostores/react'
import { reactCount, increment, decrement } from '../stores/independentCounterStore'

export function ReactCounter() {
  const count = useStore(reactCount)

  return (
    <div>
      <button onClick={() => decrement(reactCount)}>-</button>
      <span>{count}</span>
      <button onClick={() => increment(reactCount)}>+</button>
    </div>
  )
}
<!-- src/components/VueCounter.vue -->
<template>
  <div>
    <button @click="decrement(vueCount)">-</button>
    <span>{{ count }}</span>
    <button @click="increment(vueCount)">+</button>
  </div>
</template>

<script setup>
import { useStore } from '@nanostores/vue'
import { vueCount, increment, decrement } from '../stores/independentCounterStore'

const count = useStore(vueCount)
</script>
<!-- src/components/SvelteCounter.svelte -->
<script>
import { svelteCount, increment, decrement } from '../stores/independentCounterStore'
</script>

<div>
  <button on:click={() => decrement(svelteCount)}>-</button>
  <span>{$svelteCount}</span>
  <button on:click={() => increment(svelteCount)}>+</button>
</div>
---
import { astroCount, increment, decrement } from '../stores/independentCounterStore'
---

<div>
  <button id="decrement">-</button>
  <span id="count">{astroCount.get()}</span>
  <button id="increment">+</button>
</div>

<script>
  import { astroCount, increment, decrement } from '../stores/independentCounterStore'

  document.getElementById('decrement').addEventListener('click', () => decrement(astroCount))
  document.getElementById('increment').addEventListener('click', () => increment(astroCount))

  astroCount.subscribe(value => {
    document.getElementById('count').textContent = value
  })
</script>

Comme vous pouvez le voir, chaque composant de framework maintient son propre compteur d’état indépendant en utilisant Nanostores. Cette approche permet une gestion isolée de l’état à l’intérieur de chaque composant, indépendamment du framework utilisé.

Maintenant, explorez comment Nanostores permet le partage de l’état entre les composants de différents frameworks. Cela est particulièrement utile lorsque vous devez synchroniser l’état entre différentes parties de votre application.

Nous créerons un compteur partagé qui peut être mis à jour et affiché via des composants React, Vue, Svelte et Astro.

Tout d’abord, créez-nous notre propre magasin de compteur partagé :

// src/stores/sharedCounterStore.js
import { atom } from 'nanostores'

export const sharedCount = atom(0)

export function increment() {
  sharedCount.set(sharedCount.get() + 1)
}

export function decrement() {
  sharedCount.set(sharedCount.get() - 1)
}

Maintenant, implémentons des composants dans chaque framework qui utilisent cet état partagé :

// src/components/ReactSharedCounter.jsx
import { useStore } from '@nanostores/react'
import { sharedCount, increment, decrement } from '../stores/sharedCounterStore'

export function ReactSharedCounter() {
  const count = useStore(sharedCount)

  return (
    <div>
      <h2>React Shared Counter</h2>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}
<!-- src/components/VueSharedCounter.vue -->
<template>
  <div>
    <h2>Vue Shared Counter</h2>
    <button @click="decrement">-</button>
    <span>{{ count }}</span>
    <button @click="increment">+</button>
  </div>
</template>

<script setup>
import { useStore } from '@nanostores/vue'
import { sharedCount, increment, decrement } from '../stores/sharedCounterStore'

const count = useStore(sharedCount)
</script>
<!-- src/components/SvelteSharedCounter.svelte -->
<script>
import { sharedCount, increment, decrement } from '../stores/sharedCounterStore'
</script>

<div>
  <h2>Svelte Shared Counter</h2>
  <button on:click={decrement}>-</button>
  <span>{$sharedCount}</span>
  <button on:click={increment}>+</button>
</div>
---
import { sharedCount, increment, decrement } from '../stores/sharedCounterStore'
---

<div>
  <h2>Astro Shared Counter</h2>
  <button id="shared-decrement">-</button>
  <span id="shared-count">{sharedCount.get()}</span>
  <button id="shared-increment">+</button>
</div>

<script>
  import { sharedCount, increment, decrement } from '../stores/sharedCounterStore'

  document.getElementById('shared-decrement').addEventListener('click', decrement)
  document.getElementById('shared-increment').addEventListener('click', increment)

  sharedCount.subscribe(value => {
    document.getElementById('shared-count').textContent = value
  })
</script>

Avec cette configuration, tous ces composants vont partager le même état du compteur. Incrémenter ou décrémenter le compteur dans n’importe quel composant mettra à jour la valeur dans tous les composants, indépendamment du framework utilisé.

Les états indépendants et partagés sont puissants, mais parfois, nous avons besoin que notre état perdure entre rechargements de page ou même entre sessions navigateur. C’est où @nanostores/persistent entre en jeu. Explorons comment implémenter un état persistant dans notre projet Astro.

Premièrement, nous devons installer l’extension de persistence pour Nanostores :

# Using npm
npm install @nanostores/persistent

# Using yarn
yarn add @nanostores/persistent

# Using pnpm
pnpm add @nanostores/persistent

Maintenant, créez un compteur persistant qui maintiendra sa valeur même lorsque la page est rechargée :

// src/stores/persistentCounterStore.js
import { persistentAtom } from '@nanostores/persistent'

export const persistentCount = persistentAtom('persistentCount', 0)

export function increment() {
  persistentCount.set(persistentCount.get() + 1)
}

export function decrement() {
  persistentCount.set(persistentCount.get() - 1)
}

export function reset() {
  persistentCount.set(0)
}

Dans cet exemple, ‘persistentCount’ est la clé utilisée pour stocker la valeur dans localStorage, et 0 est la valeur initiale.

Implémentons un compteur persistant en utilisant des composants de frameworks différents. Ce compteur maintiendra sa valeur across rechargements de page et sera accessible à partir de n’importe quel framework.

// src/components/ReactPersistentIncrement.jsx
import { useStore } from '@nanostores/react'
import { persistentCount, increment } from '../stores/persistentCounterStore'

export function ReactPersistentIncrement() {
  const count = useStore(persistentCount)

  return (
    <button onClick={increment}>
      React Increment: {count}
    </button>
  )
}
<!-- src/components/VuePersistentDecrement.vue -->
<template>
  <button @click="decrement">
    Vue Decrement: {{ count }}
  </button>
</template>

<script setup>
import { useStore } from '@nanostores/vue'
import { persistentCount, decrement } from '../stores/persistentCounterStore'

const count = useStore(persistentCount)
</script>
<!-- src/components/SveltePersistentDisplay.svelte -->
<script>
import { persistentCount } from '../stores/persistentCounterStore'
</script>

<div>
  Svelte Display: {$persistentCount}
</div>
---
import { reset } from '../stores/persistentCounterStore'
---

<button id="reset-button">Astro Reset</button>

<script>
  import { persistentCount, reset } from '../stores/persistentCounterStore'

  document.getElementById('reset-button').addEventListener('click', reset)

  persistentCount.subscribe(value => {
    console.log('Persistent count updated:', value)
  })
</script>

Maintenant, vous pouvez utiliser ces composants ensemble dans une page Astro :

---
import ReactPersistentIncrement from '../components/ReactPersistentIncrement'
import VuePersistentDecrement from '../components/VuePersistentDecrement.vue'
import SveltePersistentDisplay from '../components/SveltePersistentDisplay.svelte'
---

<div>
  <h2>Persistent Counter Across Frameworks</h2>
  <ReactPersistentIncrement client:load />
  <VuePersistentDecrement client:load />
  <SveltePersistentDisplay client:load />
  <button id="reset-button">Astro Reset</button>
</div>

<script>
  import { persistentCount, reset } from '../stores/persistentCounterStore'

  document.getElementById('reset-button').addEventListener('click', reset)

  persistentCount.subscribe(value => {
    console.log('Persistent count updated:', value)
  })
</script>

Cette configuration montre un compteur persistant où :

  • React gère l’incrément

  • Vue gère le décrément

  • Svelte affiche le compte actuel

  • Astro fournit un bouton de réinitialisation

La valeur du compteur persistera entre les recharges de page, montrant ainsi le pouvoir de @nanostores/persistent pour maintenir l’état.

L’état persistant est particulièrement utile pour :

  1. Les préférences utilisateur (par exemple, les réglages du thème, les choix de langue)

  2. Les formulaires partiellement remplis (pour éviter la perte de données à la suite d’une recharge de page accidentelle)

  3. Les jetons d’authentification (pour maintenir les sessions utilisateurs)

  4. Le cache local de données fréquemment consultées

En utilisant @nanostores/persistent, vous pouvez améliorer l’expérience utilisateur en conservant les données d’état importantes entre les chargements de page et les sessions navigateurs.

Lorsque vous intégrez Nanostores dans vos projets Astro, gardez à l’esprit ces meilleures pratiques et astuces pour tirer le meilleur de cette solution de gestion d’état légère.

  • Utilisez atom pour des états simples, à valeur unique.

  • Utilisez map pour des états ressemblant à des objets avec plusieurs propriétés.

  • Utilisez computed pour des états dérivés qui dépendent d’autres stores.

  • Utilisez persistentAtom ou persistentMap lorsque vous devez que l’état perdure entre les recharges de page.

Avec la création de stores importants et monolithiques, préférez plutôt des stores plus petits et plus ciblés. Cette approche améliore la maintainabilité et la performance en permettant des mises à jour plus granulaires.

// Prefer this:
const userProfile = map({ name: '', email: '' })
const userPreferences = map({ theme: 'light', language: 'en' })

// Over this:
const user = map({ name: '', email: '', theme: 'light', language: 'en' })

Lorsque vous avez des états qui dépendent d’autres parties d’états, utilisez des stores calculés. Cela aide à maintenir votre état DRY (Don’t Repeat Yourself) et assure que l’état dérivé est toujours à jour.

import { atom, computed } from 'nanostores'

const firstName = atom('John')
const lastName = atom('Doe')

const fullName = computed(
  [firstName, lastName],
  (first, last) => `${first} ${last}`
)

Nanostores offre une excellente prise en charge de TypeScript. En l’utilisant, vous pouvez attraper les erreurs dès le début et améliorer l’expérience du développeur.

import { atom } from 'nanostores'

interface User {
  id: number
  name: string
}

const currentUser = atom<User | null>(null)

Bien que Nanostores soit léger, pensez à la performance dans les applications plus grandes. Utilisez la fonction batched pour grouper plusieurs mises à jour de store ensemble, réduisant le nombre de re-rendu.

import { atom, batched } from 'nanostores'

const count1 = atom(0)
const count2 = atom(0)

export const incrementBoth = batched(() => {
  count1.set(count1.get() + 1)
  count2.set(count2.get() + 1)
})

Lorsque vous utilisez Nanostores dans un projet Astro multi-framework, essayez de garder la logique d’état de base indépendante du framework. Cela facilite le partage d’état entre les différents composants du framework.

// stores/themeStore.js
import { atom } from 'nanostores'

export const theme = atom('light')

export function toggleTheme() {
  theme.set(theme.get() === 'light' ? 'dark' : 'light')
}

// React component
import { useStore } from '@nanostores/react'
import { theme, toggleTheme } from '../stores/themeStore'

function ThemeToggle() {
  const currentTheme = useStore(theme)
  return <button onClick={toggleTheme}>{currentTheme}</button>
}

Bien que les stores persistants soient puissants, utilisez-les avec discernement. Tous les états n’ont pas besoin de persister entre les sessions. Une utilisation excessive des stores persistants peut entraîner des comportements inattendus et des problèmes de performance potentiels.

Pour faciliter le débogage, vous pouvez utiliser la fonction onMount pour enregistrer les changements d’état :

import { atom, onMount } from 'nanostores'

const count = atom(0)

if (import.meta.env.DEV) {
  onMount(count, () => {
    count.listen((value) => {
      console.log('Count changed:', value)
    })
  })
}

Lorsque vous utilisez Nanostores dans des composants susceptibles d’être démontés, assurez-vous de nettoyer les abonnements pour éviter les fuites de mémoire.

import { useEffect } from 'react'
import { count } from '../stores/countStore'

function Counter() {
  useEffect(() => {
    const unsubscribe = count.subscribe(() => {
      // Do something
    })
    return unsubscribe
  }, [])

  // Rest of the component
}

En suivant ces bonnes pratiques et conseils, vous serez en mesure de gérer efficacement l’état dans vos projets Astro en utilisant Nanostores, quel que soit le framework que vous intégrez.

Comme nous l’avons exploré tout au long de cet article, Nanostores offre une solution puissante mais légère pour la gestion d’état dans les projets Astro, en particulier lorsque vous travaillez avec plusieurs frameworks. Récapitulons les points clés :

  1. Polyvalence : Nanostores s’intègre parfaitement avec Astro, React, Vue, Svelte et Solid, ce qui en fait un choix idéal pour les projets multi-frameworks.

  2. Simplicité

    : Avec son API direct et simple, Nanostores offre une pente d’apprentissage basse tout en fournissant des capacités robustes de gestion d’état.

  3. Flexibilité : De simples magasins atomiques à des états calculés complexes et même des stockages persistants, Nanostores s’adapte à une large gamme de besoins de gestion d’état.
  4. Performance : Sa nature légère garantit que Nanostores ne pondrra pas votre application, maintenant les avantages de performance d’Astro.
  5. Meilleures Pratiques : En suivant les directives que nous avons discutées, telles que le maintien de petits magasins centraux, l’utilisation de TypeScript et l’utilisation de magasins calculés pour les états dérivés, vous pouvez créer des systèmes de gestion d’état maintainables et efficaces.

Nanostores brille dans l’architecture d’îles de composants d’Astro, vous permettant de gérer l’état à travers des composants interactifs isolés de manière efficace. Que vous construisez une simple application Web avec quelques éléments interactifs ou une application Web complexe avec plusieurs frameworks, Nanostores fournit les outils nécessaires pour gérer l’état avec efficacité.

Lorsque vous poursuivez votre aventure avec Astro et Nanostores, souvenez-vous que la meilleure façon d’apprendre est par le fait même. Expérimentez différents types de magasins, essayez d’implémenter un état partagé à travers des frameworks, et explorez les possibilités de stockage persistant. Chaque projet apportera de nouveaux défis et des opportunités pour affiner vos compétences en gestion d’état.

Restez à l’affaire pour les prochains articles de notre série « Nanostores dans Astro : une aventure multi-framework », où nous plongerons plus profondément dans les applications pratiques et les techniques avancées de gestion d’état dans les projets Astro.

Pour approfondir votre compréhension de Nanostores et de son utilisation dans les projets Astro, consultez ces ressources précieuses :

Bon codage, et que vos projets Astro soient toujours états et performants !

Source:
https://meirjc.hashnode.dev/state-management-in-astro-a-deep-dive-into-nanostores