mcpField
T
tanstack-store

tanstack-store

github.com/tanstack-skills開発361.8k 回インストール39 週間前に更新
リポジトリ

tanstack-store について

TanStack Storeは、TanStackライブラリや対応するフレームワークアダプターを使う開発者向けのリアクティブな状態管理ライブラリです。ストア、計算値、エフェクト、バッチ更新を備えています。

主な機能

  1. 01リアクティブな状態ストア
  2. 02Derivedによる計算値
  3. 03クリーンアップ対応のエフェクト
  4. 04バッチ更新
  5. 05フレームワークアダプターとReact hooks

活用シーン

  1. 01アプリケーションの共有状態管理
  2. 02フィルターやソート済みデータの導出
  3. 03状態変化に応じた副作用の実行
  4. 04関連する更新の通知をまとめる

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/tanstack-skills/tanstack-skills --skill tanstack-store

インストール後、Claude Code で/tanstack-storeを入力して読み込まれたか確認します。

詳細

概要

TanStack Store は、TanStack ライブラリの内部機能を支える軽量なリアクティブストア(シグナルに似たストア)です。状態を管理する Store、計算値を管理する Derived、副作用を管理する Effect、アトミックな更新を行う batch を提供します。フレームワークアダプターはリアクティブなフックを提供します。

コア: @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: '[email protected]',
})
状態の更新
// Function updater (immutable update)
countStore.setState((prev) => prev + 1)

userStore.setState((prev) => ({ ...prev, name: 'Bob' }))
変更の購読
const unsub = countStore.subscribe(() => {
  console.log('Count:', countStore.state)
})

// Cleanup
unsub()
Store のオプション
const store = new Store(initialState, {
  // Custom update function
  updateFn: (prevValue) => (updater) => {
    return updater(prevValue) // custom logic
  },
  // Callback on subscribe
  onSubscribe: (listener, store) => {
    console.log('New subscriber')
    return () => console.log('Unsubscribed')
  },
  // Callback on every update
  onUpdate: () => {
    console.log('State updated:', store.state)
  },
})
Store のプロパティ
store.state      // Current state
store.prevState  // Previous state
store.listeners  // Set of listener callbacks

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],
})

// MUST mount to activate
const unmount = doubled.mount()

console.log(doubled.state) // 10

count.setState(() => 10)
console.log(doubled.state) // 20

// Cleanup
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)
    // Optionally return cleanup function
    return () => console.log('Cleaning up')
  },
  eager: false, // true = run immediately on mount
})

const unmount = logger.mount()

count.setState(() => 1) // logs: "Count changed: 1"

unmount()
クリーンアップを伴う Effect
const timerEffect = new Effect({
  deps: [intervalStore],
  fn: () => {
    const id = setInterval(() => { /* ... */ }, intervalStore.state)
    return () => clearInterval(id) // cleanup on next run or unmount
  },
})

Batch

複数の更新をまとめて 1 回の通知にします。

import { batch } from '@tanstack/store'

// Subscribers fire only once with final state
batch(() => {
  countStore.setState(() => 1)
  nameStore.setState(() => 'Alice')
  settingsStore.setState((prev) => ({ ...prev, theme: 'dark' }))
})

React との統合

useStore フック
import { useStore } from '@tanstack/react-store'

// Subscribe to full state
function Counter() {
  const count = useStore(countStore)
  return <button onClick={() => countStore.setState((c) => c + 1)}>{count}</button>
}

// Subscribe with selector (performance optimization)
function UserName() {
  const name = useStore(userStore, (state) => state.name)
  return <span>{name}</span>
}

// Subscribe to 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() {
  // Without shallow: re-renders on ANY state change (new object ref)
  // With shallow: only re-renders when items actually change
  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,
})

// Actions as plain functions
export function increment() {
  counterStore.setState((c) => c + 1)
}

export function reset() {
  counterStore.setState(() => 0)
}

フレームワークアダプター

フレームワークパッケージフック/コンポーザブル
React@tanstack/react-storeuseStore(store, selector?, equalityFn?)
Vue@tanstack/vue-storeuseStore(store, selector?)(computed ref を返します)
Solid@tanstack/solid-storeuseStore(store, selector?)(シグナルを返します)
Angular@tanstack/angular-storeinjectStore(store, selector?)(シグナルを返します)
Svelte@tanstack/svelte-storeuseStore(store, selector?)($state を返します)

ベストプラクティス

  1. ストアはモジュールレベルで定義します - シングルトンです
  2. useStore ではセレクターを使います。不要な再レンダリングを防げます
  3. セレクターがオブジェクト/配列を返す場合は shallow を使います
  4. Derived と Effect のインスタンスでは、必ず mount() を呼び出します
  5. アンマウント関数は必ずクリーンアップします(特に React の useEffect 内)
  6. 状態を直接変更しないでください - 必ず setState を使います
  7. 関連する複数の更新には batch を使います
  8. データ変換には Derived の連鎖を使います(フィルター -> ソート -> ページネーション)
  9. タイマー/リスナーには、Effect の fn からクリーンアップ関数を返します
  10. 可能な場合はプリミティブ値を選択します(等価性関数は不要です)

よくある落とし穴

  • Derived/Effect の mount() を忘れる(有効になりません)
  • サブスクリプション/アンマウント関数をクリーンアップしない(メモリーリークにつながります)
  • setState を使わずに store.state を直接変更する
  • shallow を使わずに、セレクターで新しいオブジェクト参照を作成する
  • セレクターを指定せずに useStore を使う(すべてを購読します)
  • Effect をすぐに実行する必要があるのに eager: true を忘れる

コメント