在Astro中state management:深入探究nanostores

歡迎來到《Nanostores in Astro: A Multi-Framework Adventure》系列的的第一部分。如果你在多框架Astro项目中挣扎于状态管理,那么你将会喜欢今天的内容。今天,我们将探索Nanostores,这是一个与Astro的组件岛架构无缝集成的轻量级状态管理解决方案。

在本文中,我们将深入探讨Nanostores如何能够跨Astro、React、Vue和Svelte组件简化状态管理。我们将涵盖独立状态、共享状态,并介绍持久的状态管理。让我们踏上简化Astro项目状态管理的旅程。

如果你急于开始,这里有一个快速摘要和一些关键链接:

請自由探索本文档中的示例和代碼,以獲得實踐學習的體驗!

NanoStores 是一個最小主義的狀態管理庫,它是為框架無關性而被設計的。它提供了一個直觀的 API,用於創建和管理原子狀態片段,這可以輕鬆地在應用程序的不同部分之間共享和更新。

  1. 輕量且快速: NanoStores 非常小巧 (介於 265 到 814 字節之間),確保它不會增加您的捆绑大小。

  2. 框架無關性: 非常适合 Astro 的多框架生態系統。它與 React、Vue、Svelte、Solid 和原生 JavaScript 無缝集成。

  3. 簡單的 API: 沒有複雜的設定或样板代码。它直觀且易於使用。

  4. 與 Astro 的元件島相辅相成: NanoStores 增強了 Astro 的島嶼建築,允許在孤立的互動元件之間高效地管理狀態。

微型倉庫环绕三大主要概念進行:

  1. 原子:持有單一值的最簡單倉庫。

  2. 映射:持有带有多個屬性的对象的倉庫。

  3. 計算仓库:基於其他倉庫計算其值的派生倉库。

讓我們看一下一個快速的例子:

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

在這個片段中,我們為計數器創建了一個原子,為用戶數據創建了一個映射,以及一個計算仓库用於动态问候。很简单,對吧?

在您的Astro项目中開始使用Nanostores是直接的。這是如何做到的:

  1. 首先,安装Nanostores及其框架整合:
# 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. 為您的倉庫創建一個新文件,譬如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. 現在您可以在您的任何組件中使用這個倉庫。這裡是一個在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>

就這樣!您剛好在您的Astro项目中設置了一個Nanostore。

在多框架的 Astro 專案中,你可能希望在不同框架的組件中獨立管理狀態。Nanostores 讓這變得無縫。我們來探索如何在 React、Vue、Svelte 和 Astro 組件中實現獨立的狀態管理。

我們將在每個框架中實現一個簡單的計數器來展示獨立狀態管理。

首先,讓我們創建我們的獨立計數器儲存:

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

現在,讓我們在每個框架中實現這個計數器:

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

如你所見,每個框架組件都使用 Nanostores 維持其自己的獨立計數器狀態。這種方法允許在每個組件中進行隔離的狀態管理,而不論使用的是哪個框架。

現在,讓我們探索 Nanostores 如何啟用跨不同框架組件的共享狀態。當你需要在應用程式的各個部分之間同步狀態時,這特別有用。

我們將創建一個可以在 React、Vue、Svelte 和 Astro 組件中更新和顯示的共享計數器。

首先,讓我們創建我們的共享計數器儲存:

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

現在,讓我們在每個框架中實現使用此共享狀態的組件:

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

在這個設定中,所有這些元件將共享同一個計數器狀態。在任何元件中增加或減少計數器都会在整个元件中更新值,無論使用哪個框架。

雖然独立和共享状态很强大,但有時我們需要讓我們的狀態在頁面重新加載或甚至瀏覽器會話之間持續存在。這時@nanostores/persistent就派上用場了。讓我們探求生態中的持久化状态如何實現。

首先,我們需要為Nanostores安裝一個持久化插件:

# Using npm
npm install @nanostores/persistent

# Using yarn
yarn add @nanostores/persistent

# Using pnpm
pnpm add @nanostores/persistent

現在,讓我們創建一個即使在頁面重新加載時也会保持其值的持久化計數器:

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

在這個例子中,「persistentCount」是用於在localStorage中存儲值的鍵,而0是初始值。

讓我們使用不同框架的元件来实现一个持久化计数器。这个计数器将保持其值在页面重新加載时不变,并且可以从任何框架中访问。

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

现在,您可以在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>

这个设定展示了一个持久化计数器,其中:

  • React 处理递增

  • Vue 处理递减

  • Svelte 顯示當前計數

  • Astro 提供重置按鈕

計數器的值將在地毯重新加載後持續存在,展現了 @nanostores/persistent 在维持狀態方面的力量。

持久化狀態特别有用於:

  1. 用戶偏好(例如,佈局設定,語言選擇)

  2. 部分已完成表單(防止因意外页面刷新而丢失数据)

  3. 身份验证令牌(用于维持用户会话)

  4. 频繁访问数据的本地缓存

通过利用 @nanostores/persistent,您可以在页面加载和浏览器会话之间维持重要的状态数据,从而提升用户体验。

在将 Nanostores 整合到您的 Astro 项目中时,请记住这些最佳实践和技巧,以充分利用这个轻量级的状态管理解决方案。

  • 使用 atom 处理简单、单一值的状态。

  • 用於有多個屬性的物件狀態時使用 map

  • 用於依賴其他商店的衍生狀態時使用 computed

  • 當需要状态在页面重载后持续存在时,使用 persistentAtompersistentMap

不要创建大型、单块的商店,而应选择更小、更专注的商店。这种方法通过允许更细粒度的更新,提高了可维护性和性能。

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

当你的状态依赖于其他状态片段时,使用计算商店。这有助于保持你的状态DRY(不要重复自己),并确保派生状态始终是最新的。

import { atom, computed } from 'nanostores'

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

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

Nanostores对TypeScript支持良好。使用它来尽早捕获错误并改善开发者体验。

import { atom } from 'nanostores'

interface User {
  id: number
  name: string
}

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

虽然Nanostores轻量级,但在大型应用程序中要留意性能。使用 batched 函数将多个商店更新组合在一起,减少重绘次数。

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

在多框架的 Astro 專案中使用 Nanostores 時,儘量保持核心狀態邏輯與框架無關。這樣可以更容易地在不同的框架組件之間共享狀態。

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

雖然持久性儲存非常強大,但要謹慎使用。並非所有狀態都需要在會話之間持久化。過度使用持久性儲存可能會導致意外行為和潛在的性能問題。

為了更方便地進行調試,可以使用 onMount 函數來記錄狀態變化:

import { atom, onMount } from 'nanostores'

const count = atom(0)

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

在可以卸載的組件中使用 Nanostores 時,請確保清理訂閱以防止記憶體洩漏。

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

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

  // Rest of the component
}

通過遵循這些最佳實踐和提示,您將能夠在使用 Nanostores 的 Astro 專案中有效地管理狀態,無論您正在集成哪種框架。

正如我們在本文中所探討的,Nanostores 為 Astro 專案中的狀態管理提供了一個強大且輕量的解決方案,特別是在與多個框架一起使用時。讓我們回顧一下關鍵要點:

  1. 多功能性:Nanostores 無縫集成了 Astro、React、Vue、Svelte 和 Solid,使其成為多框架專案的理想選擇。
  2. 簡潔性

    : 透過其直觀的 API,Nanostores 提供了低學習曲線,同時仍保有強大的狀態管理功能。

  3. 靈活性:從簡單的原子商店到複雜的計算狀態,甚至还包括持久的存儲,Nanostores 能夠適應各種狀態管理的需要。

  4. 性能:其輕量化的本性確保了 Nanostores 不會使你的應用程序變得過於庞大,並維持 Astro 的性能優勢。

  5. 最佳實踐: 通過遵循我們討論的指導原則,例如保持商店小巧且聚焦、運用 TypeScript,以及使用計算商店來衍生狀態,您可以創造出可維護且高效的狀態管理体系。

纳米商店在Astro的零件島結構中闪耀,讓您可以有效地管理局州跨隔離的互動元件。無論您正在建立一個具有幾個互動元素的簡單網站,還是一個具有多個框架的複雜網絡應用程序,纳米商店都提供您处理州所需的工具。

隨著您與Astro和纳米商店繼續您的旅程,請記住,學習的最佳方式是親身實踐。嘗試不同的商店類型,嘗試在框架之間實現共享州,並探索持久存儲的可能性。每個項目將帶來新的挑戰和機會,以優化您的州管理技巧。

請密切关注我們在 “Astro中的纳米商店:多框架冒險” 系列中的下一篇文章,我們將深入探討Astro项目中州管理的實際應用和進階技術。

為了加深您對纳米商店及其在Astro項目中的使用的理解,請查看這些有價值的資源:

快樂編程,祝您的Astro項目永葆狀態並高性能!

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