はじめに
SynState は TypeScript/JavaScript 向けの軽量でグリッチフリーな状態管理ライブラリです。シンプルなグローバル状態から、debounce や switchMap を使った複雑な非同期パイプラインまで統一 API で記述できます。React Compiler と完全互換。
「SynState」は「Synchronized + State」に由来します。グリッチフリーな伝搬により、派生値は常に一貫した状態に保たれます — 不整合な中間状態は発生しません。(詳細: SynState がグリッチを解決した方法)
- 🎯 シンプルな状態管理: React の
useState/useReducerに似た使いやすいcreateStateとcreateReducer(グローバル状態向け)。 - 🔧 非同期オペレーター内蔵:
debounce、throttle、switchMapなど — 複雑な非同期処理を外部ライブラリなしに統一的な宣言的 API で記述。 - ⚡ operator を通してもグリッチフリー: 更新はどれも、届く先を深さ順にたどる 1 回のパスです。
debounceやswitchMapが後から始める更新も同じです。そのため subscriber が更新途中のグラフを見ることはなく、1 回の更新のコストはノード間の経路の数ではなく、届くノードの数に比例します。effect で書いた operator が何を失うかは Signals の上の operator を参照。 - 🏎️ 非常に高速: ベンチマークの概要では、SynState はどのシナリオでも最速のライブラリの 2.7 倍以内に収まります。すべてのシナリオで計測したライブラリのうち、最悪のケースでも1桁倍に収まるのは SynState だけです。ライブラリごとの最悪のケースはベンチマークを参照。
- 🚀 軽量: ~7.7 kB min+gzip、外部ランタイム依存は ts-data-forge と ts-std-forge の 2 つ(前者が後者に依存している)。ほかのライブラリと比べる。
- ⚛️ React 最適化: React Compiler と完全互換。
synstate-react-hooksにより数行でコンポーネントにグローバル状態を導入。 - 🌐 フレームワーク非依存: コアはバニラ JavaScript、Vue、Svelte など任意のフレームワークでそのまま動作。React / Preact 向けには hooks ラッパー(
synstate-react-hooks/synstate-preact-hooks)、Preact 向けにはさらに Preact Signals 連携(synstate-preact-signals)も用意。フレームワークを使わない場合は、synstate-dom が Observable を DOM に直接束縛します。
createState を1回呼ぶだけで、アプリケーションにグローバル状態を追加できます:
import { createState } from 'synstate';
// リアクティブな状態を作成const [state, setState] = createState(0);
// 変更を購読state.subscribe((count) => { console.log(count); // 0, 1});
// 状態を更新setState(1);createState はリアクティブな状態(InitializedObservable)とセッター関数を作成します。サブスクライバーは初期値で即座に呼び出され、状態が更新されるたびに再び呼び出されます。
createState は3番目の要素として追加のユーティリティも返します:
const [ state, setState, { updateState, resetState, getSnapshot, initialState },] = createState(0);
updateState((prev) => prev + 2); // 前の値を使って更新getSnapshot(); // 現在の値を同期的に読み取り(2)initialState; // createState に渡した初期値(0)
resetState(); // 初期値(0)にリセットgetSnapshot(); // 現在の値を同期的に読み取り(0)pipe で値を派生する
Section titled “pipe で値を派生する”SynState の力は Observable の合成にあります。pipe で新しい値を派生し、derive で複数のソースから 1 つの値を計算します:
import { createState, derive, type InitializedObservable, map } from 'synstate';
const [count, setCount] = createState<number>(0);
// 現在の値を取得console.log(count.getSnapshot().value); // 0
// pipe で新しい Observable を派生const doubled: InitializedObservable<number> = count.pipe(map((n) => n * 2));
// 複数の Observable から 1 つの値を派生const combined: InitializedObservable<string> = derive( [count, doubled], (c, d) => `(${c}, ${d})`,);
// 変更を購読count.subscribe((value) => { console.log('count:', value); // 0, 1, 2, 3});
doubled.subscribe((value) => { console.log('doubled:', value); // 0, 2, 4, 6});
combined.subscribe((value) => { console.log(value); // "(0, 0)", "(1, 2)", "(2, 4)", "(3, 6)"});
// 状態を更新setCount(1);setCount(2);setCount(3);この宣言的なモデルがどのように機能するか、なぜ命令的に状態を管理するよりも優れているかについては、宣言的な状態管理を参照してください。
React で使う
Section titled “React で使う”createState はモジュールスコープ(コンポーネントの外)で呼び出す必要があります。SynState は synstate-react-hooks というコンパニオンパッケージを提供しており、Observable と React を橋渡しします:
import type * as React from 'react';import { createState } from 'synstate-react-hooks';
const [useUserState, setUserState] = createState({ name: '', email: '',});
const UserProfile = (): React.JSX.Element => { const user = useUserState();
return ( <div> <p>{`Name: ${user.name}`}</p> <button onClick={() => { setUserState({ name: 'Alice', email: 'alice@example.com', }); }} > {'Set User'} </button> </div> );};詳細は React 連携を参照してください。
次のステップ
Section titled “次のステップ”- インストール — SynState とオプションのコンパニオンパッケージをインストール。
- SynState の強み — 設計思想、グリッチフリー保証、ユースケース。
- 宣言的な状態管理 — 具体的な例でリアクティブプログラミングモデルを理解する。
- React 連携 — SynState を React で使う。