状态管理的终局:不是Redux,也不是Context

前端圈对状态管理的执念,可能是过去十年最大的集体焦虑。从Flux到Redux,从MobX到Recoil,从Context API到Zustand,每隔一两年就会冒出一个"更简洁"的方案,然后大家又开始新一轮的重构和争论。

但我越来越怀疑一件事:我们在用前端的方式解决本不该存在的前端问题。

 

状态管理焦虑的根源

 

大部分前端开发者第一次接触Redux的时候,都会有类似的困惑:为什么一个简单的计数器需要写这么多代码?action、reducer、dispatch、connect,整整一套ceremony,就为了让组件拿到一个数字。

当时流行的解释是:"这是为了可维护性。" 等项目大了你就懂了。

确实,等项目大了,我懂了。但我懂的不是Redux的好处,而是我们把太多不该放在前端的状态放在了前端。

举个真实场景:用户的投资组合数据。

在一个典型的React应用里,这个数据会怎么处理?
- 登录后从API获取,存到Redux store
- 用户切换持仓和理财产品,更新store
- 组件A显示总资产,组件B显示收益率,组件C显示持仓明细
- 所有组件都从store读数据,用selector优化性能
- 担心数据过期,定时轮询刷新
- 用户刷新页面,数据丢了,重新请求
- 多个tab打开,数据不同步,加个localStorage
- localStorage和Redux数据冲突,再加个hydration逻辑

这套流程下来,代码量轻松破千行。然后团队还得花时间讨论:要不要上Redux Toolkit?要不要上RTK Query?要不要上Recoil?

但如果你有后端经验,你会发现一个很荒谬的事实:这些问题在服务端早就有成熟的解决方案,甚至根本不是问题。

 

服务端视角的降维打击

 

从服务端视角看前端状态管理,你会发现很多"前端最佳实践"其实是在重新发明轮子,而且发明得还不太好。

数据一致性问题? 服务端用数据库事务解决,ACID保证。前端呢?靠reducer纯函数和immutable更新,出了bug还不好查。

数据共享问题? 服务端直接操作数据库或缓存,所有请求看到的都是最新数据。前端呢?Redux store是单实例的,多tab怎么办?localStorage同步?BroadcastChannel?还是干脆让用户刷新?

数据持久化问题? 服务端数据天然持久化。前端呢?刷新就没了,只能缓存到IndexedDB或localStorage,然后还得处理缓存失效和版本不一致。

权限控制问题? 服务端在数据层就能控制,用户根本看不到没权限的数据。前端呢?Redux store里全是数据,得靠render时判断,一不小心就泄露敏感信息。

这些问题的共同点是:它们本质上是服务端问题,却因为前后端分离和单页应用的架构,被强行推到了前端。

 

真正的问题不是状态管理工具,而是状态本身

 

我们团队在重构理财业务的时候,做过一个实验:把Redux store里的状态分类统计,发现80%的状态都属于这三类:

1. 服务端数据的本地缓存(用户信息、资产数据、产品列表)
2. 临时的UI状态(弹窗开关、tab选中、loading状态)
3. 表单数据(输入框内容、校验错误)

只有20%是真正的"应用状态",比如购买流程的步骤、筛选条件的组合、用户的操作历史。

这个发现很重要:如果80%的状态根本不需要复杂的管理,为什么要为了这20%给整个应用加上Redux的复杂度?

更进一步,那80%的状态,真的需要"管理"吗?

服务端数据其实不需要前端管"理",只需要"取"和"更新"。用户资产数据的真实状态在服务端数据库里,前端只是展示。我们把它放到Redux store,其实是在维护一个随时可能过期的缓存,然后为了保证"一致性",写了一堆刷新逻辑。

UI状态其实不需要全局管"理",组件内部useState就够了。弹窗开关、tab选中这些状态,生命周期就在组件挂载到卸载之间,根本没必要提升到全局store。提升到全局反而增加了耦合,组件卸载了状态还在,下次挂载还得记得重置。

表单数据其实不需要自己管"理",有成熟的表单库(React Hook Form、Formik)。这些库已经解决了校验、错误处理、提交等问题,比自己写reducer靠谱多了。

 

后端经验带来的三个洞察

 

做了几年BFF层之后,我对前端状态管理有了三个不一样的认知。

 

1. 服务端数据应该保持"远程"特性

 

Redux的哲学是把所有数据拉到本地,统一管理。但这其实是在对抗数据的远程特性。

服务端数据本质上是远程的、异步的、可能失败的。你把它放到Redux store,就得处理loading状态、error状态、缓存失效、乐观更新、冲突解决等一大堆问题。这些问题React Query或SWR已经解决了,而且解决得更好,因为它们接受数据是远程的这个事实。

我们现在的做法是:服务端数据用React Query,根本不进Redux。

```javascript
// 之前的Redux方式
const assets = useSelector(state => state.assets.data);
const loading = useSelector(state => state.assets.loading);
useEffect(() => {
dispatch(fetchAssets());
}, []);

// 现在的React Query方式
const { data: assets, isLoading } = useQuery('assets', fetchAssets);
```

代码量少了一半,功能还更强(自动重试、缓存、后台刷新、窗口焦点刷新)。更重要的是,你不用再维护那些action和reducer了。

 

2. 状态应该尽可能靠近数据源

 

这是服务端架构的基本原则:不要复制数据,而要引用数据源。

但前端经常违反这个原则。比如用户资产数据,真实数据源在服务端,前端Redux store只是一个拷贝。然后为了保持这个拷贝"最新",你得写很多同步逻辑。

更好的做法是:接受前端只是视图层,不要试图在前端维护"完整"的数据。

需要数据的时候就fetch,让React Query缓存它。需要更新的时候就发请求,让服务端更新后自动invalidate缓存。这样数据源始终是服务端,前端不用担心一致性问题。

 

3. 全局状态应该是极少数

 

在服务端架构里,全局状态是很昂贵的东西,因为它意味着所有服务都能访问,容易造成耦合和竞争条件。所以好的架构会尽量减少全局状态,多用局部状态或者通过消息传递。

前端也应该这样。Redux鼓励你把所有东西都放到全局store,但这不是个好主意。全局状态意味着:
- 任何组件都能读写,难以追踪数据流
- 组件之间产生隐式依赖
- 测试变困难,因为你得mock整个store
- 性能优化更难,因为一个小改动可能触发大量组件重渲染

我们现在的原则是:只有真正需要跨页面、跨路由共享的状态才放全局。 比如用户登录信息、主题设置、语言偏好。这些状态全局加起来可能就几KB,根本不需要Redux那套重型工具,Zustand或者Jotai就够了。

 

实践中的状态分层策略

 

基于这些思考,我们现在的状态管理是分层的:

服务端数据层(React Query)
用户资产、产品列表、订单历史等所有服务端数据,全部用React Query管理。它们不是"状态",是"查询"。

全局应用状态层(Zustand)
用户登录信息、主题、语言等少量全局状态,用Zustand管理。代码量小到可以放在一个文件里。

本地组件状态层(useState/useReducer)
UI状态、表单中间态、临时数据等,用组件内部state管理。生命周期和组件绑定,组件卸载状态就没了,天然避免内存泄漏。

表单状态层(React Hook Form)
所有表单用React Hook Form,包括校验、提交、错误处理。不用自己写reducer,不用手动管理field状态。

URL状态层(Router)
筛选条件、页码、排序方式等,直接放URL参数。好处是刷新不丢失,还能分享链接。

这套分层下来,整个应用的状态管理代码从之前的2000多行Redux逻辑,变成了200行Zustand store + React Query配置。代码少了90%,但功能更强,bug更少。

 

反思:前端为什么总在重复造轮子?

 

前端社区有个特点:特别喜欢造轮子,尤其是状态管理。每隔一两年就有人说"现有方案太复杂了,我发明了一个更简洁的",然后社区就兴高采烈地学习新工具、重构老代码。

但很少有人问:为什么我们需要这么多状态管理工具?

我觉得一个重要原因是:前端开发者普遍缺少服务端经验,不知道很多"前端问题"其实是"架构问题"。

举个例子,当你纠结"这个状态该放Redux store还是组件state"的时候,如果你有后端经验,你会意识到这个问题的本质是:这个数据的生命周期和作用域是什么? 这是服务端架构里的基本问题,有成熟的设计原则(单一职责、最小权限、按需加载)。

再比如,当你纠结"怎么保证多个组件的状态一致"的时候,如果你懂后端的数据一致性理论,你会知道:强一致性是有代价的,很多时候最终一致性就够了。 前端不是数据库,不需要ACID保证,用户刷新一下页面看到最新数据就行了。

全栈经验的价值不是说你要同时写前后端代码,而是你知道:
- 哪些问题应该在哪一层解决
- 哪些"最佳实践"其实是在绕弯路
- 什么时候该用简单方案,什么时候该上复杂架构

 

一个不太政治正确的结论

 

如果让我给前端状态管理一个建议,那就是:大部分应用根本不需要Redux或者类似的复杂状态管理方案。

你需要的可能只是:
- React Query管服务端数据
- 一点点Zustand管全局状态(可能就几十行)
- 组件内部useState管UI状态
- React Hook Form管表单

这套组合简单、直接、够用。

Redux不是不好,它解决的问题是真实的(大型应用的状态管理、时间旅行调试、状态回放)。但大部分应用遇不到这些问题,却提前背上了Redux的复杂度。这就像你要去超市买菜,却开了一辆18轮大卡车。

技术选型的本质是权衡,不是追求"正确"。 没有银弹,只有在特定场景下的合适方案。

当你下次开始一个新项目,考虑要不要上Redux的时候,不妨先问自己:
- 我真的需要全局状态管理吗?还是只是习惯了?
- 服务端数据为什么要放前端store?React Query不能解决吗?
- 这个状态的生命周期是什么?真的需要跨页面共享吗?

如果这些问题的答案都是否定的,那你可能不需要Redux。不用Redux不是偷懒,而是你真正理解了状态管理的本质。

前端的进步不应该是工具越来越多,而是我们越来越清楚什么时候该用什么工具,以及什么时候不该用工具。

You voted 3. Total votes: 32

添加新评论