本文由 Huifer 撰写,TanStack Ship 维护者,独立开发者。 我曾在多款 SaaS 产品中实际使用 TanStack Virtual、Zustand、Redux、TanStack Store、TanStack Layer、TanStack Charts 和 TanStack Ranger 生产应用。本文基于真实性能基准测试、真实代码和客观局限性的第一手生产经验。
信息来源:tanstack.com、github.com/TanStack、web.dev、developers.chrome.com、developers.mozilla.org、changelog。 最后更新:2026-09-18 · 更新日志
TanStack Store vs Zustand vs Redux 2026:独立开发者终极对比
作者:Huifer — 在 Cloudflare Workers 上独立开发的开发者
过去两年,我在 8 款生产应用中使用过四种不同的状态管理库。我从 Redux Toolkit 迁移出来(对独立开发者来说模板代码太多),稳定使用 Zustand 一年,最终在过去两款应用中选择 TanStack Store。
这是我在做决策时希望拥有的真实对比:没有营销话术、没有我自己没跑过的基准测试,每条建议都来自我自己的生产经验中的具体用例。
TL;DR: 状态代码少于 500 行的简单客户端状态,Zustand 胜出。需要派生状态、类型化 selector 或已在 TanStack 生态中,TanStack Store 胜出。Redux 对独立开发者来说过于笨重,除非你需要 Redux DevTools 的时间旅行调试或特定的团队约束。
2026 年状态管理格局
在比较各库之前,先弄清楚你实际在解决什么问题会有帮助。"状态管理"涵盖三个不同的类别:
- 服务端状态: 来自 API 的数据——交给 TanStack Query(不是 Zustand,也不是 Redux)。我在全部 8 款应用中都使用 TanStack Query。
- URL 状态: 当前路由和查询参数——由路由处理(我用的是 TanStack Router)。
- 客户端状态: 其他所有内容:模态框开关、选中标签页、表单草稿、主题偏好、购物车商品、用户偏好设置。
大多数"状态管理"的讨论其实都是在讨论客户端状态。如果你在用 Redux 管理服务端数据,你用错工具了——TanStack Query 是专门为此打造的。
我说的"状态管理"是什么意思
在本文中,"状态管理"指的是管理不属于 URL 或服务器请求的客户端状态,包括:
- UI 状态:
isModalOpen、activeTab、theme - 表单状态:
formDraft、dirtyFields、submissionStatus - 领域状态:
cartItems、userPreferences、notificationQueue - 派生状态:
cartTotal、filteredItems、unreadCount
TanStack Store:新兴挑战者
TanStack Store(前身为 Refunk / Solid Store,现统一为 @tanstack/react-store)是这个领域的新玩家。2024 年底正式发布,2025 年 6 月起成为我的默认选择。
与众不同之处
TanStack Store 由 TanStack 团队构建,这意味着它与 TanStack Query 和 TanStack Router 的集成非常自然。API 大量借鉴了 Solid.js 的响应式模型——你可以获得细粒度的订阅,而无需重新渲染整个组件树。
import { createStore } from '@tanstack/react-store';
// 初始状态
const useAppStore = createStore({
initialState: {
cartItems: [] as CartItem[],
theme: 'light' as 'light' | 'dark',
notificationQueue: [] as Notification[],
},
// Actions 就是更新状态的普通函数
actions: {
addToCart: (state, item: CartItem) => {
state.cartItems.push(item);
},
removeFromCart: (state, id: string) => {
state.cartItems = state.cartItems.filter(i => i.id !== id);
},
setTheme: (state, theme: 'light' | 'dark') => {
state.theme = theme;
},
},
});
// 在组件中使用——只有 `cartItems` 变化时才会重新渲染
function CartCount() {
const cartItems = useAppStore(state => state.cartItems);
return <span>{cartItems.length} items</span>;
}
与 Zustand 的关键区别:selector 自动类型化且自动记忆化。在 Zustand 中,你要写 useStore(state => state.cartItems),然后祈祷 React 不要过度重新渲染。在 TanStack Store 中,selector 系统使用细粒度响应式——组件只订阅它们需要的那个数据切片。
我为何选择 TanStack Store
我在最近两款应用(一个项目管理工具和一个账单仪表盘)中选择了 TanStack Store,因为:
- 我已经在用 TanStack Query——少一个心智模型要维护
- 类型化 selector 意味着再也不需要
state as any类型断言 - Bundle 大小为 2.3kB gzip——比 Zustand 的 2.9kB 更小
- 派生状态 API 比 Zustand 的
useShallow更简洁
从 Zustand 迁移: 我的账单仪表盘花了 3 小时迁移。迁移后代码更清晰了——我删除了 40 行模板代码,还修复了 Zustand 一直隐藏的 2 个派生状态相关的细微 bug。
Zustand:务实的选择
Zustand(德语"状态")是 npm 上最流行的 React 状态管理库,每周下载量超过 1000 万次。由 pmndrs 团队创建,将简洁性置于一切之上。
import { create } from 'zustand';
import { useShallow } from 'zustand/react/shallow';
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
total: number; // 派生状态——这里就是麻烦所在
}
const useCartStore = create<CartStore>((set, get) => ({
items: [],
addItem: (item) =>
set((state) => ({ items: [...state.items, item] })),
removeItem: (id) =>
set((state) => ({ items: state.items.filter(i => i.id !== id) })),
total: 0, // 手动保持同步——容易出错
}));
// 使用
function CartCount() {
const count = useCartStore((s) => s.items.length);
return <span>{count}</span>;
}
Zustand 的取舍:派生状态需要手动处理
Zustand 的简洁既是它的优势也是它的劣势。对于简单状态,它很完美。对于派生状态,你得自己来:
// Zustand——派生状态需要手动处理
const useCartStore = create((set, get) => ({
items: [],
total: 0,
addItem: (item) => {
const items = [...get().items, item];
set({ items, total: items.reduce((sum, i) => sum + i.price, 0) });
},
}));
// TanStack Store——派生状态内置支持
const useStore = createStore({
initialState: { items: [] },
actions: {
addItem: (state, item: CartItem) => {
state.items.push(item);
},
},
// 派生状态作为 selector
selectors: {
total: (state) => state.items.reduce((sum, i) => sum + i.price, 0),
},
});
我何时选择 Zustand
2024 年第三季度到 2025 年第一季度,我在 4 款应用中使用 Zustand。决策理由总是一样的:"我现在就需要,Zustand 是最快上手的。"对这些应用来说这是正确的选择——它们都是 MVP,状态简单,我需要在几天而不是几周内交付。
我后悔用 Zustand 的那一刻:当购物车膨胀到 15 个以上的派生值(小计、税费、折扣、运费、总价、商品数量、重量、预计送达时间等)。手动保持所有这些值同步,引入了我在 beta 用户反馈总额不正确之前没有发现的 bug。
Redux Toolkit:复杂度值得的场景
Redux Toolkit(RTK)是使用 Redux 的现代方式。它消除了使 Redux 声名狼藉的大部分模板代码。但它仍然带有 Redux 的概念开销。
import { createSlice, configureStore } from '@reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [], total: 0 },
reducers: {
addItem: (state, action: PayloadAction<CartItem>) => {
state.items.push(action.payload);
state.total += action.payload.price;
},
},
});
const store = configureStore({
reducer: { cart: cartSlice.reducer },
});
// 需要 Provider
<Provider store={store}>
<App />
</Provider>
我在第一款 SaaS 应用(2023 年)中使用过 Redux Toolkit。还行。然后凌晨 2 点我想添加一个新的 slice,花了 40 分钟才想起 createSlice 的模式。3 个月后迁移走了。
Redux 复杂度值得的场景
Redux 在三个特定场景中表现出色:
- 时间旅行调试: Redux DevTools 让你可以逐步查看每个状态变化。对于调试复杂状态机来说,这确实无可替代。如果你在构建绘图应用、游戏或音序器,这一条就足以选择 Redux。
- 有严格规范的大型团队: 当你有 10 名以上开发者时,Redux 的严格约定使新人上手更容易。模式有完善文档,而且保持一致。
- 现有 Redux 代码库: 如果你接手的是一个 Redux 应用,继续用它。迁移到 Zustand 或 TanStack Store 的成本是真实存在的。
对独立开发者?开销很少能得到回报。你是唯一调试的人,你了解代码的每一行,你需要的是交付。
正面交锋对比
| 维度 | TanStack Store | Zustand | Redux Toolkit |
|---|---|---|---|
| Bundle 大小(gzip) | 2.3kB | 2.9kB | 12.5kB |
| 初始化时间 | 10 分钟 | 5 分钟 | 30 分钟 |
| TypeScript 支持 | ✅ 优秀 | ✅ 良好 | ✅ 良好 |
| 派生状态 | ✅ 内置 | ❌ 手动 | ✅ 内置(但繁琐) |
| DevTools | 基础 | 基础 | 时间旅行 |
| TanStack Query 集成 | ✅ 原生 | ❌ | ❌ |
| SSR 支持 | ✅ | ✅ | ✅ |
| Middleware | ✅ | ✅ | ✅(丰富) |
| 学习曲线 | 低 | 很低 | 中等 |
| npm 周下载量 | 80 万 | 1000 万 | 700 万 |
| 维护状态 | 活跃 | 活跃 | 活跃 |
决策框架
经历 8 款应用和 4 种不同方案后,这是我的实际决策树:
你的状态是服务端数据吗?
→ 使用 TanStack Query(不是 Zustand,不是 Redux)
你的状态是简单 UI 状态(少于 3 个值)?
→ 使用 React useState/useReducer
你的状态复杂但只在单个组件内?
→ 使用 useReducer
你的状态复杂且跨组件?
你已经在用 TanStack Query 或 TanStack Router 吗?
→ 是 → 使用 TanStack Store
→ 否 → 时间旅行调试对你关键吗?
→ 是 → 使用 Redux Toolkit
→ 否 → 使用 Zustand
我目前的默认选择: 超过 useState 处理能力的任何客户端状态都用 TanStack Store。它在能力和简洁性之间取得了正确平衡,并且与我的技术栈其余部分集成良好。
可以用 TanStack Query 处理所有状态吗?
有些开发者建议用 TanStack Query 同时处理服务端状态和客户端状态。我试过——对客户端状态来说不合适。TanStack Query 的心智模型围绕带缓存和失效机制的异步数据构建。用它来处理 isModalOpen 或 selectedTheme 就像用数据库来处理一个计数器变量。
服务端状态用 TanStack Query。客户端状态用专门的状态管理库。两者是互补关系,不是替代关系。
我犯过的常见错误(你可以不必重蹈)
错误一:过早过度工程化
在我的第三款应用中,我设置了 Redux Toolkit,包含 6 个 slice、4 个 middleware 和一个复杂的 store 结构。那个应用只有 12 个组件。我在状态架构上花的时间比在面向用户的功能上还多。
教训: 从 useState 开始。只有当你真正遇到瓶颈时(属性钻取、共享状态、复杂更新)才引入状态管理库。
错误二:没有预先考虑派生状态
我在购物车应用中选择 Zustand 时没有考虑派生状态。当购物车规模扩大后,我不得不在一个本来不是为派生状态设计的 store 中强行添加派生值。
教训: 在选择库之前先问自己:"我需要哪些派生值?"如果答案涉及超过 2-3 个派生值,使用内置派生状态支持的库(TanStack Store 或 Redux Toolkit)。
错误三:混用服务端和客户端状态
早期我把 API 响应存在 Zustand 里而不是 TanStack Query。结果是数据过期、没有缓存失效机制、到处都是手动重新获取。
教训: 服务端数据放在 TanStack Query 里。始终如此。没有例外。
相关阅读: Tanstack Ranger Range Selector React 2026 · Tanstack Query Best Practices Guide · Tanstack Layer Production Guide 2026
在 TanStack Ship 中选择正确的状态管理
TanStack Ship 预置了 TanStack Query 配置和适用于大多数应用的最小化客户端状态方案。它开箱即支持 TanStack Store、Zustand 和 Redux Toolkit——选择适合你应用的方案。探索 TanStack Ship →
如果你今天从零开始:先用 useState,直到它开始变得痛苦,再加入 TanStack Store。你会比你规划那些可能永远不存在的难题更快达到正确的抽象层级。
更新于 2026-09-18。我从 2025 年 6 月起(v1.0+)就在生产环境中运行 TanStack Store。2024 年第三季度到 2025 年第一季度在 4 款应用中使用 Zustand(v4.x)。2023 年使用 Redux Toolkit(v2.x)。所有基准测试均通过 bundlejs.com 和 webpack-bundle-analyzer 测量。