컨텐츠로 건너뛰기

아일랜드 간 상태 공유

아일랜드 아키텍처 / 부분 수화를 사용하여 Astro 웹 사이트를 구축할 때 다음 문제에 직면했을 수 있습니다: 컴포넌트 간 상태를 공유하고 싶습니다.

React 또는 Vue와 같은 UI 프레임워크는 다른 컴포넌트가 사용하도록 “context” providers를 권장할 수 있습니다. 하지만 Astro 또는 Markdown에서 컴포넌트를 부분적으로 수화하는 경우 이러한 컨텍스트 래퍼를 사용할 수 없습니다.

Astro는 공유 클라이언트 측 스토리지를 위한 다른 솔루션인 Nano Stores를 권장합니다.

Nano Stores 라이브러리를 사용하면 모든 컴포넌트가 상호 작용할 수 있는 저장소를 작성할 수 있습니다. 다음과 같은 이유로 Nano Store를 추천합니다.

  • 가볍습니다. Nano Stores는 종속성이 전혀 없는 최소한의 필수 JS (1KB 미만)를 제공합니다.
  • 프레임워크에 구애받지 않습니다. 이는 프레임워크 간 상태 공유가 원활하다는 것을 의미합니다! Astro는 유연성을 기반으로 구축되었으므로 여러분의 선호도와 상관없이 유사한 개발자 경험을 제공하는 솔루션을 좋아합니다.

그래도 탐색할 수 있는 대안이 많이 있습니다. 여기에는 다음이 포함됩니다.

시작하려면 즐겨 사용하는 UI 프레임워크용 도우미 패키지와 함께 Nano Stores를 설치하세요.

Terminal window
npm install nanostores @nanostores/preact

여기에서 Nano Stores 사용 안내서로 이동하거나 아래 예시를 따라갈 수 있습니다!

사용 예 - 전자상거래 장바구니 플라이아웃

섹션 제목: 사용 예 - 전자상거래 장바구니 플라이아웃

세 가지 대화형 요소로 간단한 전자상거래 인터페이스를 구축한다고 가정해 보겠습니다.

  • “add to cart” 제출 양식
  • 추가된 항목을 표시하는 장바구니 플라이아웃
  • 장바구니 플라이아웃 토글

완성된 예시를 컴퓨터에서 또는 StackBlitz를 통해 온라인으로 사용해 보세요!

기본 Astro 파일은 다음과 같습니다:

src/pages/index.astro
---
import CartFlyoutToggle from '../components/CartFlyoutToggle';
import CartFlyout from '../components/CartFlyout';
import AddToCartForm from '../components/AddToCartForm';
---
<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>
<header>
<nav>
<a href="/">Astro storefront</a>
<CartFlyoutToggle client:load />
</nav>
</header>
<main>
<AddToCartForm client:load>
<!-- ... -->
</AddToCartForm>
</main>
<CartFlyout client:load />
</body>
</html>

CartFlyoutToggle을 클릭할 때마다 CartFlyout을 열어 시작해 보겠습니다.

먼저 저장소를 포함할 새 JS 또는 TS 파일을 만듭니다. 이를 위해 “atom”을 사용합니다.

src/cartStore.js
import { atom } from 'nanostores';
export const isCartOpen = atom(false);

이제 이 저장소를 읽거나 써야 하는 모든 파일로 가져올 수 있습니다. CartFlyoutToggle을 연결하는 것부터 시작하겠습니다.

src/components/CartFlyoutToggle.jsx
import { useStore } from '@nanostores/preact';
import { isCartOpen } from '../cartStore';
export default function CartButton() {
// `useStore` 후크를 사용하여 저장소 값을 읽습니다.
const $isCartOpen = useStore(isCartOpen);
// `.set`을 사용하여 가져온 저장소에 쓰기
return (
<button onClick={() => isCartOpen.set(!$isCartOpen)}>Cart</button>
)
}

그런 다음 CartFlyout 컴포넌트에서 isCartOpen을 읽을 수 있습니다.

src/components/CartFlyout.jsx
import { useStore } from '@nanostores/preact';
import { isCartOpen } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
return $isCartOpen ? <aside>...</aside> : null;
}

이제 장바구니에 담긴 품목을 추적해 보겠습니다. 중복을 방지하고 “수량”을 추적하기 위해 항목 ID를 키로 사용하여 장바구니를 객체로 저장할 수 있습니다. 이를 위해 Map를 사용하겠습니다.

앞서 cartStore.jscartItem 저장소를 추가해 보겠습니다. 원하는 경우 TypeScript 파일로 전환하여 모양을 정의할 수도 있습니다.

src/cartStore.js
import { atom, map } from 'nanostores';
export const isCartOpen = atom(false);
/**
* @typedef {Object} CartItem
* @property {string} id
* @property {string} name
* @property {string} imageSrc
* @property {number} quantity
*/
/** @type {import('nanostores').MapStore<Record<string, CartItem>>} */
export const cartItems = map({});

이제 컴포넌트가 사용할 addCartItem 도우미를 내보내 보겠습니다.

  • 해당 품목이 장바구니에 없으면 시작 수량 1로 품목을 추가하세요.
  • 해당 항목이 이미 존재하는 경우, 수량을 1 늘립니다.
src/cartStore.js
...
export function addCartItem({ id, name, imageSrc }) {
const existingEntry = cartItems.get()[id];
if (existingEntry) {
cartItems.setKey(id, {
...existingEntry,
quantity: existingEntry.quantity + 1,
})
} else {
cartItems.setKey(
id,
{ id, name, imageSrc, quantity: 1 }
);
}
}

저장소가 준비되면 해당 양식이 제출될 때마다 AddToCartForm 내에서 이 함수를 호출할 수 있습니다. 또한 전체 장바구니 요약을 볼 수 있도록 장바구니 플라이아웃을 열겠습니다.

src/components/AddToCartForm.jsx
import { addCartItem, isCartOpen } from '../cartStore';
export default function AddToCartForm({ children }) {
// 단순화를 위해 항목 정보를 하드코딩하겠습니다!
const hardcodedItemInfo = {
id: 'astronaut-figurine',
name: 'Astronaut Figurine',
imageSrc: '/images/astronaut-figurine.png',
}
function addToCart(e) {
e.preventDefault();
isCartOpen.set(true);
addCartItem(hardcodedItemInfo);
}
return (
<form onSubmit={addToCart}>
{children}
</form>
)
}

마지막으로 CartFlyout 내부에 장바구니 항목을 렌더링합니다.

src/components/CartFlyout.jsx
import { useStore } from '@nanostores/preact';
import { isCartOpen, cartItems } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
const $cartItems = useStore(cartItems);
return $isCartOpen ? (
<aside>
{Object.values($cartItems).length ? (
<ul>
{Object.values($cartItems).map(cartItem => (
<li>
<img src={cartItem.imageSrc} alt={cartItem.name} />
<h3>{cartItem.name}</h3>
<p>Quantity: {cartItem.quantity}</p>
</li>
))}
</ul>
) : <p>Your cart is empty!</p>}
</aside>
) : null;
}

이제 은하계에서 가장 작은 JS 번들이 포함된 완전한 대화형 전자상거래 예시가 생겼습니다. 🚀

완성된 예시를 컴퓨터에서 또는 StackBlitz를 통해 온라인으로 사용해 보세요!