Nanostores: atom、map、computedの違いと使い方

こんにちは。フロントエンドエンジニアの馬場です。

近年、フロントエンド開発の状態管理の変遷は目まぐるしいものがあります。 その中で、今回プロジェクトに採用したNanostoresについてご紹介します。

はじめに

フロントエンド開発において、状態管理はアプリケーションの一貫性とパフォーマンスを保つために重要です。Nanostoresは、軽量で効率的な状態管理ライブラリとして、小規模から中規模のアプリケーションに最適です。本記事では、Nanostoresのatom、map、computedの違いと、それぞれの使い方について詳しく説明します。

Nanostoresの基本コンセプト

Nanostoresは、状態管理のための3つの主要なコンセプトを提供します。それぞれのコンセプトは異なる用途に適しており、アプリケーションのニーズに応じて使い分けることができます。

atom

atomは、単純な値を管理するためのストアです。数値や文字列など、単一の値を保持するのに適しています。

import { atom } from 'nanostores';

// 単一の値を保持するストアを作成
const counter = atom(0);

// 状態を更新するための専用関数
const setCounter = (value) => {
  // 状態を設定する前に必要なロジックを追加
  if (value >= 0) {
    counter.set(value);
  } else {
    console.warn('Counter value cannot be negative');
  }
};

// 値の取得
console.log(counter.get()); // 0

// 値の設定
setCounter(1);
console.log(counter.get()); // 1

// 無効な値の設定(例)
setCounter(-1); // 警告が表示され、値は変更されない
console.log(counter.get()); // 1

setメソッドを直接呼び出さないことのメリット

1. トレーシングとデバッグが容易になる

setメソッドを直接呼び出すのではなく、専用の関数(例えばsetCounter)を用意することで、状態の変更がどこで行われているのかを追跡しやすくなります。これにより、デバッグ時に特定の状態変更がどこで発生したかを簡単に特定できます。

2. 一貫性の確保

専用の関数を使用することで、状態を変更する際の一貫性を保つことができます。例えば、状態を変更する前に必ず特定の検証やサイドエフェクトを実行する場合、これを関数内に組み込むことができます。

3. ロジックのカプセル化

状態を変更するためのロジックを関数内にカプセル化することで、コードの可読性と保守性が向上します。これにより、状態の更新に関するロジックを一箇所に集約でき、変更が必要な場合もその関数だけを修正すれば済みます。

4. 再利用性の向上

特定のロジックを持つ関数を作成することで、同じロジックを他の部分でも再利用しやすくなります。これにより、コードの重複を避けることができます。

このように、専用の関数を用意することで、状態管理がより安全で管理しやすくなります。特に大きなプロジェクトや複雑な状態管理が必要な場合には、この方法が非常に有効です。 これはmapについても同じことが言えます。

map

mapは、オブジェクトの状態を管理するためのストアです。複数のプロパティを持つオブジェクトを扱う場合に便利です。

import { map } from 'nanostores';

interface User {
  name: string;
  age: number;
}

// オブジェクトを保持するストアを作成
const user = map<User>({ name: 'Alice', age: 25 });

// 状態を更新するための専用関数
const setUserProperty = <K extends keyof User>(key: K, value: User[K]): void => {
  if (key in user.get()) {
    user.setKey(key, value);
  } else {
    console.warn(`Property ${key} does not exist on user`);
  }
};

// プロパティの取得
console.log(user.get().name); // Alice

// プロパティの更新
setUserProperty('age', 26);
console.log(user.get().age); // 26

// 無効なプロパティの設定(例)
setUserProperty('height' as keyof User, 170 as any); // 警告が表示され、値は変更されない

このようにオブジェクトの型をきちんと定義することで、ストアを安全に運用することができます。

computed

computedは、他のストアの値に基づいて新しい値を計算するためのストアです。依存するストアが変更されると、自動的に再計算されます。

import { atom, computed } from 'nanostores';

// 基本のストアを定義
const price = atom<number>(100);
const quantity = atom<number>(2);

// computedを使って新しいストアを作成
const total = computed<number, [number, number]>(
  [price, quantity],
  (price, quantity) => price * quantity
);

// 使用例
console.log(total.get()); // 200

// 値を変更して再計算
price.set(150);
console.log(total.get()); // 300

まとめ

  • atom: 単一の値を管理するときに使用します。シンプルなカウンターやフラグなどに適しています。
  • map: 複数のプロパティを持つオブジェクトを管理するときに使用します。ユーザー情報や設定オブジェクトなどに適しています。
  • computed: 他のストアの値に基づいて動的に計算される値が必要なときに使用します。集計値や派生データの計算に適しています。

いかがでしたでしょうか。 Nanostoresは、シンプルで効率的な状態管理を提供するライブラリです。 atommapcomputedの3つのコンセプトを理解し、適切に使い分けることで、アプリケーションの状態管理をより直感的に行うことができます。 それぞれの特性を活かして、プロジェクトのニーズに合った状態管理を実現できるといいですね!