mcpField
T
tanstack-store

tanstack-store

github.com/tanstack-skills开发361.8k 次安装39 周前更新
代码仓库

关于 tanstack-store

TanStack Store 是面向使用 TanStack 库或受支持框架适配器的开发者的响应式状态库。它提供状态存储、计算值、副作用和批量更新。

主要功能

  1. 01响应式状态存储
  2. 02使用 Derived 计算值
  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

将多次更新合并为一次通知:

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>
}
浅比较函数

当选择器返回结构相等的对象时,避免重新渲染:

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?)(返回计算属性 ref)
Solid@tanstack/solid-storeuseStore(store, selector?)(返回信号)
Angular@tanstack/angular-storeinjectStore(store, selector?)(返回信号)
Svelte@tanstack/svelte-storeuseStore(store, selector?)(返回 $state)

最佳实践

  1. 在模块级别定义 Store - 它们是单例
  2. 在 useStore 中使用选择器,避免不必要的重新渲染
  3. 当选择器返回对象/数组时使用 shallow
  4. 始终对 Derived 和 Effect 实例调用 mount()
  5. 始终清理卸载函数(尤其是在 React useEffect 中)
  6. 切勿直接修改状态 - 始终使用 setState
  7. 多次相关更新时使用 batch
  8. 使用 Derived 链进行数据转换(筛选 -> 排序 -> 分页)
  9. 在 Effect 的 fn 中返回清理函数,用于计时器/监听器
  10. 尽可能选择基本类型(无需 equality fn)

常见问题

  • 忘记对 Derived/Effect 调用 mount()(它们不会激活)
  • 未清理订阅/卸载函数(会造成内存泄漏)
  • 直接修改 store.state,而不是使用 setState
  • 在选择器中创建新的对象引用,却没有使用 shallow
  • 不带选择器使用 useStore(会订阅所有内容)
  • Effect 应立即运行时忘记设置 eager: true

评论

支持 Markdown