コンテンツにスキップ

SynState の強み

シンプルに始められ、必要に応じてパワフルに

Section titled “シンプルに始められ、必要に応じてパワフルに”

SynState は Web フロントエンド向けの状態管理ライブラリです。ほとんどのユースケースでは、createStatecreateReducer、そして combinemap のようなシンプルなコンビネーターだけで十分です — React の useState / useReducer と同じくらい直感的でありながら、グローバル状態を管理できるクリーンで最小限の API です。例えば、シンプルなカウンターはわずか数行で実装できます:

import { createState } from 'synstate-react-hooks';
const [useCount, , { updateState }] = createState(0);
const Counter = () => {
const count = useCount();
return <button onClick={() => updateState((n) => n + 1)}>{count}</button>;
};

要件がより複雑になっても、SynState は対応できます。シンプルなダークモード切替から自動キャンセル付きデバウンス検索パイプラインまで、すべてを統一された API で記述できます。

実際の状態管理は単純な get/set にとどまりません。フィルター入力にはデバウンスが、API 呼び出しにはキャンセルが必要で、複数のソースが同時に変化したときに派生値は一貫性を保たなければなりません。これらの要件には、非同期データフロー依存関係の自動伝播を宣言的に表現できるシステムが求められます。

SynState は Observable パターンの上に構築されています。状態を、合成・変換・結合できる値のストリームとしてモデル化するパターンです。これにより、RxJS のような別のライブラリを必要とせず、豊富なオペレーター(debouncethrottleswitchMapmergeMap 等)を利用できます。

実際にどのように機能するか、命令的なコードとの比較については宣言的な状態管理を参照してください。

高パフォーマンス、グリッチフリー

Section titled “高パフォーマンス、グリッチフリー”

Observable パターン自体は新しいものではありません。RxJS が何年もかけて普及させてきました。しかし RxJS にはグリッチ問題という根本的な正確性の課題があります。複数の派生値が共通のソースを持つとき、combineLatest は一部の入力が更新されたが他はまだ古いままの不整合な中間状態を emit してしまいます。この問題は依存グラフが複雑になるほど深刻化します。例えば、1つのソースから複数の派生値を作り再び結合する「ダイアモンド型」の依存関係では、結合の数に応じて冗長な計算が O(n2)O(n^2) にスケールし、さらにこのダイアモンドが直列に連なると O(Dn)O(D^n)DD = 1段あたりの分岐数)と指数関数的に増大します。

SynState はこれを深さ順伝播アルゴリズムで解決しています。すべての派生値がその祖先すべてが更新された後にのみ更新されることを保証し、O(n)O(n) の単一パスで処理します。グリッチなし、冗長な計算なし、優先度キューも不要です。

特徴RxJSSynState
グリッチフリーNo — combineLatest が中間状態を emit するYes — depth 順の伝播により一貫性を保証
伝播コストツリーでは O(n)O(n)、ダイアモンドが直列に連なると最大 O(Dn)O(D^n)DD = 1段あたりの分岐数)すべてのケースで O(n)O(n)
InitializedObservableなし — 最初の emission まで値がない場合がある組み込み — 常に値を保持し、状態の表現に最適
設計の焦点汎用的な非同期イベント処理状態管理が第一、非同期オペレーター(debounceswitchMap 等)も完全サポート

詳細な説明は SynState はグリッチをどう解決したかを参照してください。

SynState のすべてのリアクティブ計算(combinemapfilter など)は React のレンダリングサイクルの外側で完了します。コンポーネントは useSyncExternalStore(React のファーストクラス API)を通じて値を読み取るだけであり、レンダリング中に暗黙的な副作用を持ちません — Proxy ベースの追跡も、プロパティアクセスによる隠れたサブスクリプションもありません。そのため、React Compiler はこれらのコンポーネントを安全に解析・メモ化でき、React Compiler の自動メモ化と完全に整合します。

MobX の observer() HOC はレンダリング中に observable へのアクセスを追跡するため、React Compiler のメモ化と衝突し、"use no memo" ディレクティブによる opt-out が必要です。SynState ではこのような回避策は不要です。

SynState が適しているケース:

  • ✅ コンポーネント間で共有するグローバル状態(ダークモード切替、ユーザーセッションなど)。
  • debouncethrottleswitchMap などのオペレーターを使った複雑な非同期状態管理。
  • ✅ Redux ライクな reducer による状態管理(createReducer)。
  • ✅ 状態管理の規模が未定のプロジェクト。SynState の統一された API は、単一の共有カウンターから完全なデバウンス検索パイプラインまですべてをカバーするため、要件が増えてもライブラリを切り替える必要がありません。
  • ✅ 型安全なイベントエミッター(createEventEmitter)。

他のソリューションを検討すべきケース:

  • コンポーネントインスタンスごとに独立した状態が必要な場合 — 例えば、フォームの各入力フィールドの値、アコーディオンの開閉状態、モーダルの表示/非表示など、同じコンポーネントが画面上に複数インスタンス存在し、それぞれが独立した状態を持つケースです。これらはグローバルステートで管理するとインスタンスの生成・破棄に合わせた配列管理が必要になりコードが複雑化するため、React hooks の useState / useReducer でコンポーネントローカルに管理するのが自然です。