tanstack-store
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOverview
概述
TanStack Store is a lightweight reactive store (signals-like) that powers the internals of TanStack libraries. It provides for state, for computed values, for side effects, and for atomic updates. Framework adapters provide reactive hooks.
StoreDerivedEffectbatchCore:
React:
@tanstack/store@tanstack/react-storeTanStack Store 是一款轻量级响应式存储库(类信号机制),为 TanStack 系列库提供内部支撑。它提供用于管理状态的 、计算值的 、处理副作用的 ,以及用于原子更新的 。框架适配器则提供响应式 Hook。
StoreDerivedEffectbatch核心包:
React 适配包:
@tanstack/store@tanstack/react-storeInstallation
安装
bash
npm install @tanstack/store @tanstack/react-storebash
npm install @tanstack/store @tanstack/react-storeStore
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 callbackstypescript
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
框架适配器
| Framework | Package | Hook/Composable |
|---|---|---|
| React | | |
| Vue | | |
| Solid | | |
| Angular | | |
| Svelte | | |
| 框架 | 包名 | Hook/组合式函数 |
|---|---|---|
| React | | |
| Vue | | |
| Solid | | |
| Angular | | |
| Svelte | | |
Best Practices
最佳实践
- Define stores at module level - they're singletons
- Use selectors in to prevent unnecessary re-renders
useStore - Use when selectors return objects/arrays
shallow - Always call on Derived and Effect instances
mount() - Always clean up unmount functions (especially in React useEffect)
- Never mutate state directly - always use
setState - Use for multiple related updates
batch - Use Derived chains for data transformations (filter -> sort -> paginate)
- Return cleanup functions from Effect for timers/listeners
fn - Select primitives when possible (no equality fn needed)
- 在模块级别定义Store - 它们是单例
- 在中使用选择器 以避免不必要的重渲染
useStore - 当选择器返回对象/数组时使用
shallow - 务必为Derived和Effect实例调用
mount() - 务必清理卸载函数(尤其是在React的useEffect中)
- 永远不要直接修改状态 - 始终使用
setState - 使用处理多个相关更新
batch - 使用Derived链式调用进行数据转换(过滤 -> 排序 -> 分页)
- 从Effect的中返回清理函数 用于处理定时器/监听器
fn - 尽可能选择原始类型(无需比较函数)
Common Pitfalls
常见陷阱
- Forgetting to Derived/Effect (they won't activate)
mount() - Not cleaning up subscriptions/unmount functions (memory leaks)
- Mutating directly instead of using
store.statesetState - Creating new object references in selectors without
shallow - Using without a selector (subscribes to everything)
useStore - Forgetting when Effect should run immediately
eager: true
- 忘记为Derived/Effect调用(它们不会激活)
mount() - 不清理订阅/卸载函数(内存泄漏)
- 直接修改而非使用
store.statesetState - 在选择器中创建新对象引用却不使用
shallow - 使用时不指定选择器(订阅所有状态变更)
useStore - 当Effect需要立即执行时忘记设置",
eager: true