
你是否还在为庞大的Redux项目维护而头疼?或者正在新项目中纠结应该选择Zustand还是Redux?本文将彻底拆解2025年React状态管理的真实困境——答案可能会颠覆你的认知。
先问你一个问题:你项目中的Redux代码到底在干什么?
很多开发者会发现,当他们开始从Redux迁移时,会惊讶地发现——整个Redux的样板代码、action、reducer、中间件、selectors……其实可以被三个不同的、更专业的工具完全替代,而且效果更好。这听起来有点荒诞,但这正是2025年的现实。
还记得那个时代吗?当所有的"状态"都被塞进Redux store里——API响应数据、URL参数、modal打开状态、表单临时值……这种大一统的思维在2010年代是标准做法。但这也是Redux最臃肿的地方。
Redux Toolkit的出现本身就能说明问题——如果Redux真的设计合理,为什么还要发明一个toolkit来"简化"它?这就像说一个正确的设计需要修补一样。
让我们把应用的"状态"重新分类。关键不是问"这个状态放在哪里",而是问"这个状态是什么性质的"。性质不同,处理方案完全不同。
这才是你Redux代码的90%所在。
想象一个典型场景:用户打开商城页面,需要加载商品列表。这看起来简单,但实际上涉及:
这所有的复杂性,Redux原生无法优雅地处理。你需要写中间件、自定义逻辑、手写缓存层……而现在,有更好的方案。
推荐方案:TanStack Query
function ProductList() {
const { isPending, error, data } = useQuery({
queryKey: ['products', category], // 关键字自动去重
queryFn: () => fetchProducts(category),
staleTime: 1000 * 60 * 5, // 5分钟内的数据被认为是"新鲜"的
});
if (isPending) return<SkeletonLoader />;
if (error) return<ErrorBoundary error={error} />;
return<ProductGrid data={data} />;
}
这一个hook解决了上面列出的所有问题。没有action、没有reducer、没有中间件。试试从Redux迁移到TanStack Query,你会流泪——因为消失的代码太多了。
官方数据显示:从Redux迁移到TanStack Query,平均消除80%的状态管理相关代码。
有多少次,你手动在Redux里同步URL参数?
// Redux的"正确做法":
const [filter, setFilter] = useState('all');
// 然后你需要在某个地方写:
useEffect(() => {
history.push(`?filter=${filter}`);
}, [filter]);
// 然后还要在另一个地方写:
useEffect(() => {
const params = new URLSearchParams(location.search);
setFilter(params.get('filter') || 'all');
}, [location.search]);
这是Redux开发者常犯的"主动找麻烦"。
更聪明的做法:使用nuqs
import { useQueryState } from 'nuqs';
function ProductFilter() {
const [filter, setFilter] = useQueryState('filter', { defaultValue: 'all' });
return (
<div>
<button onClick={() => setFilter('sale')}>特价商品</button>
{/* URL自动变成 ?filter=sale,刷新也不丢失状态 */}
</div>
);
}
URL状态本质上是持久化状态,不应该在内存store里维护。这不仅更简洁,用户还可以复制链接分享当前的筛选条件。
一个modal的打开/关闭状态需要放在Redux里吗?一个表单的输入框值需要全局管理吗?
答案是:绝对不需要。
function ProductModal() {
// 这就够了,不需要Redux
const [isOpen, setIsOpen] = useState(false);
const [formData, setFormData] = useState({ name: '', price: '' });
return (
// ...组件内容
);
}
使用Redux来管理这种状态,就像用大炮打蚊子。但中国开发者在企业项目中经常看到这种情况——整个应用的Redux store里充满了这种"本来不应该在这里"的状态。
现在我们来到了真正需要Redux的场景——多个无关的组件需要共享同一个状态。
比如:
这是真正需要全局状态管理的地方,通常也不会特别复杂。
如果你承认确实需要共享状态,那么下一个问题是:用什么库?
让我们按照硬指标来评测当前最流行的几个库:
这很重要,因为越简洁的库,当维护者消失时,迁移成本越低。
Redux 👎 - 臭名昭著的样板代码。你需要学习action types、action creators、reducer逻辑、中间件……
// Redux的繁琐
const SET_USER = 'SET_USER';
const setUser = (user) => ({ type: SET_USER, payload: user });
const userReducer = (state = null, action) => {
switch(action.type) {
case SET_USER: return action.payload;
default: return state;
}
};
Redux Toolkit 😐 - 改善了,但依然不够简洁。你还是要理解slices、thunks这些概念。
Zustand 🎉 - 赢家。两行代码就能上手:
import create from'zustand';
const useUserStore = create((set) => ({
user: null,
setUser: (user) =>set({ user }),
}));
// 使用
function UserProfile() {
const user = useUserStore((state) => state.user);
return<div>{user?.name}</div>;
}
MobX 👎 - "Observables"和"reactive programming"——对React开发者来说是陌生的概念。
Jotai 👎 - 引入了"atoms"这种抽象概念,虽然强大但学习曲线陡峭。
XState 👎 - "State machines"、"actors"、"events"——这东西的学习成本简直是天文数字。除非你在做Figma这样的复杂应用,否则没必要。
共享状态库最常见的问题是:修改了一个小状态,结果所有使用该store的组件都重新渲染。
Redux & Redux Toolkit
✅ - 通过selectors能避免,但需要手动写。Zustand
✅ - 开箱即用,自动订阅你访问的属性。
// 只有当user变化时才会重渲染,theme变化时不会触发
const user = useUserStore((state) => state.user);
Redux - 有官方支持,但逐渐在衰退。看看最新的Next.js或Fresh app模板,都没有预设Redux。
Zustand - 由主流开源社区维护(pmndrs),活跃度高。作者同时也是Jotai的维护者,说明这是个可信赖的团队。
TanStack Query - 更新频繁,有赞助商支持,生态健康。
如果你现在要启动一个新项目,完全摆脱遗留包袱,应该这样组织你的状态管理:
你的项目状态树
├── 远程数据(API来的数据)
│ └── TanStack Query 处理
├── URL状态(查询参数、页面路由)
│ └── nuqs 处理
├── 本地组件状态(modal、表单输入)
│ └── useState / useReducer 处理
└── 全局共享状态(用户信息、主题)
└── Zustand 处理
这个组合的威力在于:
import { useQuery } from'@tanstack/react-query';
import { useQueryState } from'nuqs';
import { useUserStore } from'@/store/userStore';
exportfunction ProductDetail({ productId }) {
// 远程状态:从API加载商品数据
const { data: product, isPending } = useQuery({
queryKey: ['product', productId],
queryFn: () => fetch(`/api/products/${productId}`).then(r => r.json()),
});
// URL状态:收藏/对比的商品ID存在URL里
const [compareIds, setCompareIds] = useQueryState('compare', {
defaultValue: '',
});
// 本地状态:这个组件内的切换逻辑
const [selectedTab, setSelectedTab] = useState('detail');
const [reviewSort, setReviewSort] = useState('newest');
// 全局共享状态:用户的收藏、购物车等
const { addToCart, addToWishlist } = useUserStore();
if (isPending) return<Skeleton />;
return (
<div>
<ProductImages images={product.images} />
<div>
<h1>{product.name}</h1>
<Price>{product.price}</Price>
<button onClick={() => addToCart(product)}>
加入购物车
</button>
<button onClick={() => addToWishlist(product)}>
收藏
</button>
</div>
<Tabs activeTab={selectedTab} onChange={setSelectedTab}>
<Tab label="商品详情">{/* 商品信息 */}</Tab>
<Tab label="用户评论">
<SortButton
value={reviewSort}
onChange={setReviewSort}
/>
{/* 评论列表 */}
</Tab>
</Tabs>
</div>
);
}
看这个例子,我们没有写一行Redux代码,但处理了:
不是。如果你在一个大型企业应用中,需要高度结构化和一致的代码规范(比如金融系统),Redux Toolkit的"意见强硬"反而是优势。它的Redux DevTools调试体验也无与伦比。但对于95%的应用来说,确实过度工程化了。
对于共享状态的需求,是的。但Zustand是"随意型"的,这对小团队很好,对大团队可能造成代码风格混乱。所以选择时要考虑团队规模。
分阶段迁移:
严格来说没必要。但一旦项目涉及任何复杂的API逻辑(缓存、重试、去重),TanStack Query立刻让你省掉几十行手写逻辑。性价比极高。
不要问"用什么状态管理库"。 应该问"我有什么类型的状态需要管理,每种类型最合适的工具是什么"。
这个转变可能看起来微妙,但它能彻底改变你写代码的方式:
状态类型 | 对应工具 | Redux时代 | 改进 |
|---|---|---|---|
远程数据 | TanStack Query | 50%代码 | 95%代码删除 |
URL参数 | nuqs | 手动同步 | 自动处理 |
本地状态 | useState | 放在Redux | 就用本地state |
共享状态 | Zustand | Redux | 少写70%代码 |
选择这个方案意味着:
这不是什么"银弹",但这是2026年React生态发展的必然结果。大一统的做法已经过时了。
如果你还在新项目中选择Redux,请问自己一个诚实的问题:是因为技术决定,还是因为"之前一直都这么用"?如果是后者,也许是时候尝试一下新的思路了。
下次在团队会议上提出这个观点时,你会发现很多人都有同样的想法——他们只是在等有人先挑战"Redux是标配"这个观念。
在中文开发者社区中,这个转变来得稍晚一些(因为很多教程还在教Redux),但趋势是确定的。现在是抓住这个机会的好时机。
相关资源