ניהול מדינה באסטרו: צלילה עמוקה לננו-סטורס

ברוכים הבאים לחלק ראשון של "Nanostores in Astro: A Multi-Framework Adventure" series. אם התמוטטתם עם ניהול המצב בפרוייקטים הרבים הפיסיים שלכם ב-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!'
)

בקטע זה, יצרנו אטום עבור מונה, מפה עבור נתוני משתמש וחנויות מחושבות עבור ברכה דינמית. פשוט, נכון?

התחלת עבודה עם ננוסטרס בפרויקט אסטרו שלך היא פשוטה. הנה איך עושים את זה:

  1. ראשית, התקן את ננוסטרס ואת האינטגרציות של המסגרת שלה:
# 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. עכשיו אתה יכול להשתמש בחנות זו בכל אחד מהמרכיבים שלך. הנה דוגמה מהירה במרכיב אסטרו:
---
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 עם מספר מסגרות, ייתכן שתרצה לנהל מצב עצמאי בתוך רכיבים של מסגרות שונות. 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. בואו נחקור כיצד ליישם מצב מתמשך בפרויקט Astro שלנו.

ראשית, עלינו להתקין את התוסף המתמשך 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 עבור מצבים נגזרים התלויים בחנויות אחרות.

  • השתמש ב-persistentAtom או ב-persistentMap כאשר יש צורך במצב שנשמר גם לאחר רענון הדף.

במקום ליצור חנויות גדולות ומונוליתיות, העדף חנויות קטנות וממוקדות יותר. גישה זו משפרת את התחזוקתיות והביצועים בכך שהיא מאפשרת עדכונים גרגירים יותר.

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

כאשר יש לך מצב שתלוי בחלקים אחרים של מצב, השתמש ב-computed stores. זה עוזר לשמור על DRY (Don't Repeat Yourself) ומבטיח שהמצב הנגזר תמיד מעודכן.

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

כאשר משתמשים ב-Nanostores בפרויקט Astro רב מסגרות, נסה לשמור על לוגיקת המצב המרכזית בלתי תלויה במסגרת. זה מקל על שיתוף מצב בין רכיבי מסגרת שונים.

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

על ידי ביצוע פרקטיקות וטיפים אלה, תוכל לנהל ביעילות את המצב בפרויקטים שלך עם Astro באמצעות Nanostores, ללא קשר לאילו מסגרות אתה משלב.

כפי שחקרנו במאמר זה, Nanostores מספקת פתרון חזק אך קל לניהול מצב בפרויקטים של Astro, במיוחד כשעובדים עם מסגרות מרובות. בוא נסכם את הנקודות המרכזיות:

  1. גמישות: Nanostores משתלבת בצורה חלקה עם Astro, React, Vue, Svelte ו-Solid, מה שהופך אותה לבחירה אידיאלית לפרויקטים רב-מסגרות.

  2. פשטות: עם ממשק API פשוט, Nanostores מציע עקומת למידה נמוכה ועדיין מספק יכולות ניהול מצב חזקות.

  3. גמישות: מחנויות אטומיות פשוטות ועד מצבים מחושבים מורכבים ואפילו אחסון מתמשך, Nanostores מסתגל למגוון רחב של צרכי ניהול מצב.

  4. ביצועים: הטבע הקל משקל שלו מבטיח ש-Nanostores לא יכביד על האפליקציה שלך, תוך שמירה על היתרונות של Astro בביצועים.

  5. שיטות עבודה מומלצות: על ידי עקיבה אחר ההנחיות שדיברנו עליהן, כמו שמירה על חנויות קטנות וממוקדות, שימוש ב-TypeScript, ושימוש בחנויות מחושבות למצבים נגזרים, ניתן ליצור מערכות ניהול מצב ניתנות לתחזוקה ויעילות.

ננוסטורים מבהיקים במבנה האיילנדים של אסטרו, מאפשרים לך לנהל את המצב בין רכיבים אינטראקטיביים בנפרד בצורה יעילה. בין אם אתה בונה אתר פשוט עם מספר רכיבים אינטראקטיביים או אפליקציית רשת מורכבת עם מספר פרימitives, ננוסטורים מספקים את הכלים הנחוצים לך כדי להתמודד עם המצב בצורה יעילה.

בעודך ממשיך במסע עם אסטרו וננוסטורים, זכרו שהדרך הטובה ביותר ללמוד היא דרך העשייה. נסו עם סוגים שונים של מאגרים, ניסויים ביישום מצב משותף בין פרימitives, וחקור את האפשרויות של אחסון קבעי. כל פרוייקט יביא אתגרים חדשים והזדמנויות לשיפור את היכולתים שלך בניהול המצב בפרוייקטים של אסטרו.

השאר ברגע למאמרים הבאים בסדרה "ננוסטורים באסטרו: מסע עם מספר פרימitives" בהם אנחנו נעלה יותר עמוק ליישומים מעשיים וטכניקות מתקדמות לניהול המצב בפרוייקטים של אסטרו.

כדי לעמוד בעומק יותר של ננוסטורים ושימוש בהם בפרוייקטים של אסטרו, הבחנו במשאבים הבאים:

התמקדו בקודים שלכם, ולפרוייקטים שלכם באסטרו תהיה קיימות ובעלי ביצועים!

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