Gerenciamento de Estado em Astro: Uma profunda análise de Nanostores

Bem-vindo à primeira parte da série “Nanostores in Astro: A Multi-Framework Adventure”. Se você tiver lutado com o gerenciamento de estado em seus projetos Astro multi-framework, você vai adorar. Hoje, nós estaremos explorando Nanostores, uma solução de gerenciamento de estado leve que integra-se perfeitamente com a arquitetura de ilhas de componentes do Astro.

Neste artigo, nós abordaremos como os Nanostores podem simplificar o gerenciamento de estado entre componentes Astro, React, Vue e Svelte. Vamos cobrir estado independente, estado compartilhado e apresentar o gerenciamento de estado persistente. Vamos embarcar nesta jornada para simplificar o gerenciamento de estado em nossos projetos Astro.

Se você está ansioso por uma breve visão geral, aqui está um resumo rápido e alguns links essenciais:

Fique livre para explorar a demonstração e o código ao lado deste artigo para uma experiência de aprendizagem prática!

Nanostores é uma biblioteca de gerenciamento de estado minimalista projetada com agnosticismo de framework em mente. Ela fornece uma API simples para criar e gerenciar pequenos pedaços de estado atômicos, que podem ser facilmente compartilhados e atualizados em diferentes partes do seu aplicativo.

  1. Leve e Rápido: Nanostores é incrivelmente pequeno (Entre 265 e 814 bytes), garantindo que não vai encher o tamanho do pacote de seu aplicativo.

  2. Agnóstico em Framework: Perfeito para o ecossistema multi-framework de Astro. Integra-se sem problemas com React, Vue, Svelte, Solid e JavaScript Vanilla.

  3. API Simples: Sem configuração complexa ou boilerplate. É simples e intuitivo de usar.

  4. Complementar aos Ilhas de Componentes de Astro: Nanostores melhora a arquitetura de ilhas de componentes do Astro, permitindo um gerenciamento eficiente de estado através de componentes interativos isolados.

Nanostores se baseia em três conceitos principais:

  1. Átomos: Armazenamentos simples que contêm um valor único.

  2. Mapas: Armazenamentos que mantêm objetos com várias propriedades.

  3. Armazenamentos Computados: Armazenamentos derivados que calculam seu valor com base em outros armazenamentos.

Vamos olhar por um exemplo rápido:

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!'
)

Neste snippet, criamos um átomo para um contador, um mapa para dados do usuário e um armazenamento computado para um cumprimento dinâmico. Simples, não é?

Iniciar com Nanostores em seu projeto Astro é fácil. Veja como fazer:

  1. Primeiro, instale Nanostores e suas integrações de 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. Crie um novo arquivo para seus armazenamentos, digamos 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. Agora você pode usar este armazenamento em qualquer componente dele. Aqui está um exemplo rápido em um componente 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>

E lá você tem! Você acabou de configurar um Nanostore em seu projeto Astro.

Em projetos Astro multi-framework, você pode querer gerenciar estados independentemente dentro de componentes de diferentes frameworks. Nanostores torna isso sinuoso. Vamos explorar como implementar o gerenciamento de estado independente entre componentes React, Vue, Svelte e Astro.

Nós implementaremos um simples contador em cada framework para demonstrar o gerenciamento de estado independente.

Primeiro, vamos criar nosso contador independente:

// 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)
}

Agora, implementemos este contador em cada framework:

// 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>

Como você pode ver, cada componente de framework mantém seu próprio contador independente de estado usando Nanostores. Esta abordagem permite o gerenciamento de estado isolado dentro de cada componente, independentemente do framework usado.

Agora, vamos explorar como Nanostores permite o gerenciamento de estado compartilhado entre componentes de framework diferentes. Isso é particularmente útil quando você precisa sincronizar estado entre várias partes de sua aplicação.

Nós criaremos um contador compartilhado que pode ser atualizado e exibido entre componentes React, Vue, Svelte e Astro.

Primeiro, vamos criar nosso armazenamento de contador compartilhado:

// 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)
}

Agora, implementemos componentes em cada framework que usam este estado compartilhado:

// 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>

Com este setup, todos esses componentes compartilharão o mesmo estado do contador. Ao incrementar ou decrementar o contador em qualquer componente, a alteração será refletida em todos os componentes, independentemente do framework utilizado.

Enquanto estados independentes e compartilhados são poderosos, às vezes precisamos que o estado persista através de recarregamentos de página ou até mesmo por sessões do navegador. É aqui que o @nanostores/persistent entra em cena. Vamos explorar como implementar um estado persistente em nosso projeto Astro.

Primeiro, precisamos instalar o complemento persistente para Nanostores:

# Using npm
npm install @nanostores/persistent

# Using yarn
yarn add @nanostores/persistent

# Using pnpm
pnpm add @nanostores/persistent

Agora, vamos criar um contador persistente que manterá seu valor mesmo quando a página for recarregada:

// 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)
}

Neste exemplo, ‘persistentCount’ é a chave usada para armazenar o valor no localStorage, e 0 é o valor inicial.

Vamos implementar um contador persistente usando componentes de frameworks diferentes. Este contador manterá seu valor através de recarregamentos de página e será acessível de qualquer 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>

Agora, você pode usar esses componentes juntos em uma página 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>

Este setup demonstra um contador persistente onde:

  • React gerencia o incremento

  • Vue gerencia o decremento

  • Svelte exibe o contador atual

  • Astro fornece um botão de reset

O valor do contador persistirá entre recarregamentos de página, mostrando o poder do @nanostores/persistent na manutenção do estado.

O estado persistente é particularmente útil para:

  1. Preferências do usuário (ex.: definições de tema, escolhas de idioma)

  2. Formulários parcialmente preenchidos (para evitar perda de dados em recargas de página acidentais)

  3. Tokens de autenticação (para manter as sessões do usuário)

  4. Cache local de dados frequentemente acessados

Ao aproveitar o @nanostores/persistent, você pode melhorar a experiência do usuário mantendo dados estado importante entre carregamentos de página e sessões do navegador.

Enquanto integra Nanostores em seus projetos Astro, tenha estas melhores práticas e dicas em mente para fazer o máximo desta solução de gerenciamento de estado leve.

  • Use atom para estados simples, de valor único.

  • Use map para estados de objeto com várias propriedades.

  • Use computed para estados derivados que dependem de outros armazéns.

  • Use persistentAtom ou persistentMap quando você precisa que o estado persista após recarregamentos de página.

Ao invés de criar armazéns grandes e monolíticos, prefira armazéns pequenos e focados. Esta abordagem melhora a manutenção e a performance permitindo atualizações mais granulares.

// 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' })

Quando você tem estado que depende de outros pedaços de estado, use armazéns computados. Isto ajuda a manter seu estado DRY (Don’t Repeat Yourself) e garante que o estado derivado está sempre atualizado.

import { atom, computed } from 'nanostores'

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

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

Nanostores oferece excelente suporte a TypeScript. Use-o para capturar erros cedo e melhorar a experiência do desenvolvedor.

import { atom } from 'nanostores'

interface User {
  id: number
  name: string
}

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

Enquanto Nanostores é leve, tenha em mente o desempenho em aplicações maiores. Use a função batched para agrupar várias atualizações de estoque juntas, reduzindo o número de re-renderizações.

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)
})

Ao usar Nanostores em um projeto Astro com múltiplos frameworks, tente manter a lógica de estado principal independente de framework. Isso facilita o compartilhamento de estado entre diferentes componentes de 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>
}

Embora os armazenamentos persistentes sejam poderosos, use-os com cuidado. Nem todo estado precisa persistir entre sessões. O uso excessivo de armazenamentos persistentes pode levar a comportamentos inesperados e possíveis problemas de desempenho.

Para facilitar a depuração, você pode usar a função onMount para registrar mudanças de estado:

import { atom, onMount } from 'nanostores'

const count = atom(0)

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

Ao usar Nanostores em componentes que podem ser desmontados, certifique-se de limpar as inscrições para evitar vazamentos de memória.

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

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

  // Rest of the component
}

Seguindo essas práticas recomendadas e dicas, você poderá gerenciar efetivamente o estado em seus projetos Astro usando Nanostores, independentemente dos frameworks que estiver integrando.

Como exploramos ao longo deste artigo, o Nanostores oferece uma solução poderosa e leve para gerenciamento de estado em projetos Astro, especialmente ao trabalhar com múltiplos frameworks. Vamos recapitular os principais pontos:

  1. Versatilidade: O Nanostores integra-se perfeitamente com Astro, React, Vue, Svelte e Solid, tornando-o uma escolha ideal para projetos com múltiplos frameworks.

  2. Simplicidade: Com sua API simples, as Nanostores oferecem uma curva de aprendizagem baixa enquanto ainda fornecem capacidades robustas de gerenciamento de estado.

  3. Flexibilidade: De lojas atômicas simples a estados computados complexos e até mesmo armazenamento persistente, as Nanostores se adapta a uma ampla variedade de necessidades de gerenciamento de estado.

  4. Performance: Sua natureza leve garante que as Nanostores não vão encher seu aplicativo, mantendo os benefícios de performance do Astro.

  5. Melhores Práticas: Seguindo as diretrizes que mencionamos, como manter as lojas pequenas e focadas, aproveitando o TypeScript e usando lojas computadas para estados derivados, você pode criar sistemas de gerenciamento de estado eficientes e manutíveis.

Nanostores brilha na arquitetura de ilhas de componentes do Astro, permitindo que você gerencie estado em componentes interativos isolados de forma eficiente. Se você estiver construindo um simples site com alguns elementos interativos ou uma aplicação web complexa com várias frameworks, Nanostores fornece as ferramentas necessárias para gerenciar estado eficazmente.

Enquanto você prossegue sua jornada com Astro e Nanostores, lembre-se que a melhor forma de aprender é fazendo. Experimente diferentes tipos de store, tente implementar estado compartilhado entre frameworks e explorar as possibilidades de armazenamento persistente. Cada projeto trará novos desafios e oportunidades para refinar suas habilidades em gerenciamento de estado.

Fique de olho para os próximos artigos na nossa série “Nanostores no Astro: Uma Aventura com Múltiplos Frameworks”, onde nós mergulharmos mais fundo em aplicações práticas e técnicas avançadas para gerenciamento de estado em projetos Astro.

Para aprofundar seu entendimento de Nanostores e de seu uso em projetos Astro, verifique esses recursos valiosos:

Feliz programação, e que seus projetos Astro sejam sempre estados e performáticos!

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