tanstack-store
フレームワークに依存しない不変のリアクティブデータストアで、React・Vue・Solid・Angular・Svelte向けのアダプターを提供します。
description の原文を見る
Framework-agnostic, immutable reactive data store with framework adapters for React, Vue, Solid, Angular, and Svelte.
SKILL.md 本文
概要
TanStack Store は、TanStack ライブラリの内部を支える軽量なリアクティブストア(signals のような)です。状態管理用の Store、計算値用の Derived、副作用用の Effect、アトミック更新用の batch を提供します。フレームワークアダプターはリアクティブなフックを提供します。
Core: @tanstack/store
React: @tanstack/react-store
インストール
npm install @tanstack/store @tanstack/react-store
Store
Store の作成
import { Store } from '@tanstack/store'
const countStore = new Store(0)
const userStore = new Store<{ name: string; email: string }>({
name: 'Alice',
email: 'alice@example.com',
})
状態の更新
// 関数 updater(イミュータブルな更新)
countStore.setState((prev) => prev + 1)
userStore.setState((prev) => ({ ...prev, name: 'Bob' }))
変更の監視
const unsub = countStore.subscribe(() => {
console.log('Count:', countStore.state)
})
// クリーンアップ
unsub()
Store オプション
const store = new Store(initialState, {
// カスタム更新関数
updateFn: (prevValue) => (updater) => {
return updater(prevValue) // カスタムロジック
},
// 購読時のコールバック
onSubscribe: (listener, store) => {
console.log('New subscriber')
return () => console.log('Unsubscribed')
},
// 更新時のコールバック
onUpdate: () => {
console.log('State updated:', store.state)
},
})
Store プロパティ
store.state // 現在の状態
store.prevState // 前の状態
store.listeners // リスナーコールバックのセット
Derived(計算値)
import { Store, Derived } from '@tanstack/store'
const count = new Store(5)
const multiplier = new Store(2)
const doubled = new Derived({
deps: [count, multiplier],
fn: ({ currDepVals }) => currDepVals[0] * currDepVals[1],
})
// アクティブ化するために必ずマウント
const unmount = doubled.mount()
console.log(doubled.state) // 10
count.setState(() => 10)
console.log(doubled.state) // 20
// クリーンアップ
unmount()
前の値を使用した Derived
const accumulated = new Derived({
deps: [count],
fn: ({ prevVal, currDepVals }) => {
return currDepVals[0] + (prevVal ?? 0)
},
})
Derived の連鎖
const filtered = new Derived({
deps: [dataStore, filterStore],
fn: ({ currDepVals }) => currDepVals[0].filter(matchesFilter(currDepVals[1])),
})
const sorted = new Derived({
deps: [filtered, sortStore],
fn: ({ currDepVals }) => [...currDepVals[0]].sort(comparator(currDepVals[1])),
})
const paginated = new Derived({
deps: [sorted, pageStore],
fn: ({ currDepVals }) => currDepVals[0].slice(
currDepVals[1].offset,
currDepVals[1].offset + currDepVals[1].limit,
),
})
Effect(副作用)
import { Store, Effect } from '@tanstack/store'
const count = new Store(0)
const logger = new Effect({
deps: [count],
fn: () => {
console.log('Count changed:', count.state)
// オプションでクリーンアップ関数を返す
return () => console.log('Cleaning up')
},
eager: false, // true = マウント時に即座に実行
})
const unmount = logger.mount()
count.setState(() => 1) // ログ出力: "Count changed: 1"
unmount()
クリーンアップ付き Effect
const timerEffect = new Effect({
deps: [intervalStore],
fn: () => {
const id = setInterval(() => { /* ... */ }, intervalStore.state)
return () => clearInterval(id) // 次の実行またはアンマウント時にクリーンアップ
},
})
Batch
複数の更新を 1 つの通知にグループ化:
import { batch } from '@tanstack/store'
// 購読者は最終的な状態で 1 回だけ起動
batch(() => {
countStore.setState(() => 1)
nameStore.setState(() => 'Alice')
settingsStore.setState((prev) => ({ ...prev, theme: 'dark' }))
})
React インテグレーション
useStore フック
import { useStore } from '@tanstack/react-store'
// 全状態を購読
function Counter() {
const count = useStore(countStore)
return <button onClick={() => countStore.setState((c) => c + 1)}>{count}</button>
}
// セレクターで購読(パフォーマンス最適化)
function UserName() {
const name = useStore(userStore, (state) => state.name)
return <span>{name}</span>
}
// Derived を購読
function DoubledDisplay() {
const value = useStore(doubledDerived)
return <span>{value}</span>
}
shallow 等値関数
セレクターが構造的に等しいオブジェクトを返す場合、再レンダリングを防止:
import { useStore } from '@tanstack/react-store'
import { shallow } from '@tanstack/react-store'
function TodoList() {
// shallow なし: 状態変更時に再レンダリング(新しいオブジェクト参照)
// shallow あり: items が実際に変わった時のみ再レンダリング
const items = useStore(todosStore, (state) => state.items, shallow)
return <ul>{items.map(/* ... */)}</ul>
}
React で Derived/Effect をマウント
function MyComponent() {
useEffect(() => {
const unmountDerived = myDerived.mount()
const unmountEffect = myEffect.mount()
return () => {
unmountDerived()
unmountEffect()
}
}, [])
const value = useStore(myDerived)
return <span>{value}</span>
}
モジュールレベル Store パターン
// stores/counter.ts
import { Store, Derived } from '@tanstack/store'
export const counterStore = new Store(0)
export const doubledCount = new Derived({
deps: [counterStore],
fn: ({ currDepVals }) => currDepVals[0] * 2,
})
// プレーンな関数としてのアクション
export function increment() {
counterStore.setState((c) => c + 1)
}
export function reset() {
counterStore.setState(() => 0)
}
フレームワークアダプター
| フレームワーク | パッケージ | フック/コンポーザブル |
|---|---|---|
| React | @tanstack/react-store | useStore(store, selector?, equalityFn?) |
| Vue | @tanstack/vue-store | useStore(store, selector?) (computed ref を返す) |
| Solid | @tanstack/solid-store | useStore(store, selector?) (signal を返す) |
| Angular | @tanstack/angular-store | injectStore(store, selector?) (signal を返す) |
| Svelte | @tanstack/svelte-store | useStore(store, selector?) ($state を返す) |
ベストプラクティス
- モジュールレベルで store を定義 - シングルトンです
- useStore でセレクターを使用 - 不要な再レンダリングを防止
- セレクターがオブジェクト/配列を返す場合は
shallowを使用 - Derived と Effect インスタンスで必ず
mount()を呼び出す - 必ずクリーンアップ関数をアンマウント(特に React useEffect)
- 状態を直接ミューテートしない - 常に
setStateを使用 - 複数の関連する更新には
batchを使用 - データ変換には Derived チェーンを使用(filter → sort → paginate)
- Effect の
fnからクリーンアップ関数を返す (タイマー/リスナー用) - プリミティブを選択 (等値関数は不要)
よくある落とし穴
- Derived/Effect の
mount()呼び出し忘れ(アクティブ化されません) - 購読/アンマウント関数のクリーンアップ忘れ(メモリリーク)
setStateの代わりにstore.stateを直接ミューテートshallowなしでセレクターで新しいオブジェクト参照を作成- セレクターなしで
useStoreを使用(すべてを購読) - Effect が即座に実行されるべき場合に
eager: trueを忘れる
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- tanstack-skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/tanstack-skills/tanstack-skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。