Tanstack Store

作者 tanstack-skills6f5521ecbdfb无许可证35 个星标收录于 2026年10月8日更新于 2026年10月8日仓库8个月前更新

Framework-agnostic, immutable reactive data store with framework adapters for React, Vue, Solid, Angular, and Svelte.

AI 生成的概览

TanStack Store 使用参考:跨框架响应式状态库,涵盖 store、派生值、副作用、批量更新与各框架适配器。

功能
该技能是 TanStack Store 这一轻量响应式状态库的使用参考。它说明如何创建 store、以不可变方式更新状态、订阅变更,以及使用计算派生值 Derived、副作用 Effect 和批量更新 batch。内容还包括 React 的 useStore 与 shallow 相等性,以及 Vue、Solid、Angular、Svelte 的适配器钩子,并给出最佳实践与常见陷阱。
适用场景
在编写或审查使用 TanStack Store 管理共享响应式状态的代码时使用。适合解答关于 store 创建、派生值、副作用、批量更新或框架适配器钩子的问题。
运行要求
不附带脚本,仅为文档。所述库通过 npm 包安装,例如 @tanstack/store 与 @tanstack/react-store,示例使用 TypeScript 和 React。

Overview

TanStack Store is a lightweight reactive store (signals-like) that powers the internals of TanStack libraries. It provides Store for state, Derived for computed values, Effect for side effects, and batch for atomic updates. Framework adapters provide reactive hooks.

Core: @tanstack/store React: @tanstack/react-store

Installation

bash
npm install @tanstack/store @tanstack/react-store

Store

Creating a Store

typescript
import { Store } from '@tanstack/store'
const countStore = new Store(0)
const userStore = new Store<{ name: string; email: string }>({  name: 'Alice',  email: '[email protected]',})

Updating State

typescript
// Function updater (immutable update)countStore.setState((prev) => prev + 1)
userStore.setState((prev) => ({ ...prev, name: 'Bob' }))

Subscribing to Changes

typescript
const unsub = countStore.subscribe(() => {  console.log('Count:', countStore.state)})
// Cleanupunsub()

Store Options

typescript
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 Properties

typescript
store.state      // Current statestore.prevState  // Previous statestore.listeners  // Set of listener callbacks

Derived (Computed Values)

typescript
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 activateconst unmount = doubled.mount()
console.log(doubled.state) // 10
count.setState(() => 10)console.log(doubled.state) // 20
// Cleanupunmount()

Derived with Previous Value

typescript
const accumulated = new Derived({  deps: [count],  fn: ({ prevVal, currDepVals }) => {    return currDepVals[0] + (prevVal ?? 0)  },})

Chaining Derived

typescript
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 (Side Effects)

typescript
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 with Cleanup

typescript
const timerEffect = new Effect({  deps: [intervalStore],  fn: () => {    const id = setInterval(() => { /* ... */ }, intervalStore.state)    return () => clearInterval(id) // cleanup on next run or unmount  },})

Batch

Group multiple updates into one notification:

typescript
import { batch } from '@tanstack/store'
// Subscribers fire only once with final statebatch(() => {  countStore.setState(() => 1)  nameStore.setState(() => 'Alice')  settingsStore.setState((prev) => ({ ...prev, theme: 'dark' }))})

React Integration

useStore Hook

tsx
import { useStore } from '@tanstack/react-store'
// Subscribe to full statefunction 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 Derivedfunction DoubledDisplay() {  const value = useStore(doubledDerived)  return <span>{value}</span>}

shallow Equality Function

Prevents re-renders when selector returns structurally-equal objects:

tsx
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>}

Mounting Derived/Effect in React

tsx
function MyComponent() {  useEffect(() => {    const unmountDerived = myDerived.mount()    const unmountEffect = myEffect.mount()    return () => {      unmountDerived()      unmountEffect()    }  }, [])
  const value = useStore(myDerived)  return <span>{value}</span>}

Module-Level Store Pattern

typescript
// stores/counter.tsimport { 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 functionsexport function increment() {  counterStore.setState((c) => c + 1)}
export function reset() {  counterStore.setState(() => 0)}

Framework Adapters

FrameworkPackageHook/Composable
React@tanstack/react-storeuseStore(store, selector?, equalityFn?)
Vue@tanstack/vue-storeuseStore(store, selector?) (returns computed ref)
Solid@tanstack/solid-storeuseStore(store, selector?) (returns signal)
Angular@tanstack/angular-storeinjectStore(store, selector?) (returns signal)
Svelte@tanstack/svelte-storeuseStore(store, selector?) (returns $state)

Best Practices

  1. Define stores at module level - they're singletons
  2. Use selectors in useStore to prevent unnecessary re-renders
  3. Use shallow when selectors return objects/arrays
  4. Always call mount() on Derived and Effect instances
  5. Always clean up unmount functions (especially in React useEffect)
  6. Never mutate state directly - always use setState
  7. Use batch for multiple related updates
  8. Use Derived chains for data transformations (filter -> sort -> paginate)
  9. Return cleanup functions from Effect fn for timers/listeners
  10. Select primitives when possible (no equality fn needed)

Common Pitfalls

  • Forgetting to mount() Derived/Effect (they won't activate)
  • Not cleaning up subscriptions/unmount functions (memory leaks)
  • Mutating store.state directly instead of using setState
  • Creating new object references in selectors without shallow
  • Using useStore without a selector (subscribes to everything)
  • Forgetting eager: true when Effect should run immediately

来源与署名

来源:tanstack-skills/tanstack-skills位于plugins/tanstack-store/skills/tanstack-store提交6f5521e

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架