コンテンツにスキップ

アイランド間で状態を共有する

Astroのウェブサイトを アイランドアーキテクチャ / パーシャルハイドレーション で構築する際に、 「コンポーネント間で状態を共有したい」 という問題に直面することがあります。

ReactやVueなどのUIフレームワークは、他のコンポーネントなどの 「コンテキスト」プロバイダー の利用を奨励する場合があります。
ただし、AstroやMarkdown内で コンポーネントを部分的にハイドレート する場合、これらのコンテキスト ラッパーを使用することはできません。

Astroは、クライアント側の共有ストレージに別のソリューション (Nano Stores) を推奨しています。

Nano Storesライブラリを使用すると、任意のコンポーネントが相互作用できるストアを作成できます。
Nano Storesを推奨する理由は次の通りです。

  • 軽量です。 Nano Storesは必要最低限のJS(1KB未満)を依存関係なしで提供します。
  • フレームワークに依存しません。 これにより、フレームワーク間での状態共有がシームレスになります!Astroは柔軟性を重視しているため、どのフレームワークを使用しても同様の開発者体験を提供するソリューションが好きです。

それでも、あなたが探せる選択肢はいくつもあります。
選択肢はには次のものが含まれます。

使い始めるには、Nano Storesと、お好みのUIフレームワーク用のヘルパーパッケージをインストールしてください。

ターミナルウィンドウ
npm install nanostores @nanostores/preact

ここからNano Stores使用ガイドに遷移するか、以下の例に従って進めてください!

簡単なeコマースインターフェースを構築するとしましょう。
以下の3つのインタラクティブな要素があります。

  • 「カートに追加」送信フォーム
  • カートに追加されたアイテムを表示するフライアウトメニュー
  • カートフライアウトメニューのトグル

完成した例 をあなたのマシンやオンラインで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ファイルを作成して、ストアを含めます。
このために”atoms”を使用します。

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をキーとしてカートをオブジェクトとして保存できます。
これにmapsを使用します。

先ほどの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バンドルを持つ完全にインタラクティブなeコマースのサンプルが完成しました 🚀

完成した例を試してみてください 自分のマシンまたはStackBlitzでオンラインで試してみましょう!

貢献する

どんなことを?

GitHub Issueを作成

チームに素早く問題を報告できます。

コミュニティ