tanstack-store

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

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
TanStack Store 是一款轻量级响应式存储库(类信号机制),为 TanStack 系列库提供内部支撑。它提供用于管理状态的
Store
、计算值的
Derived
、处理副作用的
Effect
,以及用于原子更新的
batch
。框架适配器则提供响应式 Hook。
核心包:
@tanstack/store
React 适配包:
@tanstack/react-store

Installation

安装

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

Store

Store

Creating a Store

创建Store

typescript
import { Store } from '@tanstack/store'

const countStore = new Store(0)

const userStore = new Store<{ name: string; email: string }>({
  name: 'Alice',
  email: 'alice@example.com',
})
typescript
import { Store } from '@tanstack/store'

const countStore = new Store(0)

const userStore = new Store<{ name: string; email: string }>({
  name: 'Alice',
  email: 'alice@example.com',
})

Updating State

更新状态

typescript
// Function updater (immutable update)
countStore.setState((prev) => prev + 1)

userStore.setState((prev) => ({ ...prev, name: 'Bob' }))
typescript
// 函数式更新器(不可变更新)
countStore.setState((prev) => prev + 1)

userStore.setState((prev) => ({ ...prev, name: 'Bob' }))

Subscribing to Changes

订阅状态变更

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

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

// 清理订阅
unsub()

Store Options

Store 配置选项

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)
  },
})
typescript
const store = new Store(initialState, {
  // 自定义更新函数
  updateFn: (prevValue) => (updater) => {
    return updater(prevValue) // 自定义逻辑
  },
  // 订阅时的回调
  onSubscribe: (listener, store) => {
    console.log('新订阅者加入')
    return () => console.log('订阅已取消')
  },
  // 每次更新时的回调
  onUpdate: () => {
    console.log('状态已更新:', store.state)
  },
})

Store Properties

Store 属性

typescript
store.state      // Current state
store.prevState  // Previous state
store.listeners  // Set of listener callbacks
typescript
store.state      // 当前状态
store.prevState  // 上一状态
store.listeners  // 监听器回调集合

Derived (Computed Values)

Derived(计算值)

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 activate
const unmount = doubled.mount()

console.log(doubled.state) // 10

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

// Cleanup
unmount()
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],
})

// 必须调用mount()以激活
const unmount = doubled.mount()

console.log(doubled.state) // 10

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

// 清理
unmount()

Derived with Previous Value

基于历史值的Derived

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

Chaining Derived

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

Effect(副作用处理)

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()
typescript
import { Store, Effect } from '@tanstack/store'

const count = new Store(0)

const logger = new Effect({
  deps: [count],
  fn: () => {
    console.log('Count 已变更:', count.state)
    // 可选返回清理函数
    return () => console.log('正在清理')
  },
  eager: false, // true = 挂载后立即执行
})

const unmount = logger.mount()

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

unmount()

Effect with Cleanup

带清理逻辑的Effect

typescript
const timerEffect = new Effect({
  deps: [intervalStore],
  fn: () => {
    const id = setInterval(() => { /* ... */ }, intervalStore.state)
    return () => clearInterval(id) // cleanup on next run or unmount
  },
})
typescript
const timerEffect = new Effect({
  deps: [intervalStore],
  fn: () => {
    const id = setInterval(() => { /* ... */ }, intervalStore.state)
    return () => clearInterval(id) // 下次执行或卸载时清理
  },
})

Batch

Batch

Group multiple updates into one notification:
typescript
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' }))
})
将多个更新合并为一次通知:
typescript
import { batch } from '@tanstack/store'

// 订阅者只会在最终状态时触发一次
batch(() => {
  countStore.setState(() => 1)
  nameStore.setState(() => 'Alice')
  settingsStore.setState((prev) => ({ ...prev, theme: 'dark' }))
})

React Integration

React 集成

useStore Hook

useStore Hook

tsx
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>
}
tsx
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 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>
}
当选择器返回结构相等的对象时,避免不必要的重渲染:
tsx
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>
}

Mounting Derived/Effect in React

在React中挂载Derived/Effect

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

  const value = useStore(myDerived)
  return <span>{value}</span>
}
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

模块级Store模式

typescript
// 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)
}
typescript
// 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)
}

Framework Adapters

框架适配器

FrameworkPackageHook/Composable
React
@tanstack/react-store
useStore(store, selector?, equalityFn?)
Vue
@tanstack/vue-store
useStore(store, selector?)
(returns computed ref)
Solid
@tanstack/solid-store
useStore(store, selector?)
(returns signal)
Angular
@tanstack/angular-store
injectStore(store, selector?)
(returns signal)
Svelte
@tanstack/svelte-store
useStore(store, selector?)
(returns $state)
框架包名Hook/组合式函数
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)

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)
  1. 在模块级别定义Store - 它们是单例
  2. useStore
    中使用选择器
    以避免不必要的重渲染
  3. 当选择器返回对象/数组时使用
    shallow
  4. 务必为Derived和Effect实例调用
    mount()
  5. 务必清理卸载函数(尤其是在React的useEffect中)
  6. 永远不要直接修改状态 - 始终使用
    setState
  7. 使用
    batch
    处理多个相关更新
  8. 使用Derived链式调用进行数据转换(过滤 -> 排序 -> 分页)
  9. 从Effect的
    fn
    中返回清理函数
    用于处理定时器/监听器
  10. 尽可能选择原始类型(无需比较函数)

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
  • 忘记为Derived/Effect调用
    mount()
    (它们不会激活)
  • 不清理订阅/卸载函数(内存泄漏)
  • 直接修改
    store.state
    而非使用
    setState
  • 在选择器中创建新对象引用却不使用
    shallow
  • 使用
    useStore
    时不指定选择器(订阅所有状态变更)
  • 当Effect需要立即执行时忘记设置
    eager: true
    ",