混乱的开始
项目是 3 年前开始的,那时候 Redux 还是主流。后来新人进来,有人用 Context API,有人用 Zustand,还有人直接用 useState。时间一长,状态管理就乱套了。
最夸张的时候,一个用户信息,在 Redux store、Context、Zustand store 和组件本地状态里各存了一份。改个用户名,要同步 4 个地方,经常漏掉。
为什么选 Zustand?
决定统一状态管理的时候,考虑了几个方案:
- Redux:太复杂,写个简单状态要写一堆 boilerplate。
- Context API:性能不好,容易导致不必要的重渲染。
- Jotai:原子化状态,但学习成本高。
- Zustand:简单,性能好,TypeScript 支持好。
最后选了 Zustand,主要是因为它够简单。写个 store 就几行代码,不用配置 middleware,不用写 action creator,直接用。
迁移过程
第一步:梳理现有状态
先花了一周时间,把项目里所有的状态都列出来,看看哪些是全局的,哪些是局部的,哪些是重复的。
结果发现,80% 的状态其实都是局部的,根本不需要全局管理。只有用户信息、主题设置、通知这些真正需要全局共享。
第二步:按模块迁移
我们按功能模块一个个迁移,先迁移用户模块,再迁移主题,最后迁移通知。每个模块迁移完,都要测试一遍,确保功能正常。
迁移 Redux 的时候最麻烦,因为 Redux 的 action 和 reducer 分散在各个文件里,要找全不容易。我们写了个脚本,扫描所有 Redux 相关的 import,才把依赖关系理清楚。
第三步:清理重复状态
迁移过程中,发现很多状态是重复的。比如用户信息,在 Redux 和 Context 里都有。我们统一到一个 Zustand store 里,然后逐步替换所有引用。
遇到的坑
坑一:Redux DevTools 依赖
有些同事习惯用 Redux DevTools 调试,迁移到 Zustand 后就用不了了。虽然 Zustand 也支持 Redux DevTools,但配置起来有点麻烦。
后来我们用了 Zustand 的 middleware,加上 Redux DevTools 支持,才解决了这个问题。
坑二:中间件迁移
Redux 里用了 redux-thunk 和 redux-persist,迁移到 Zustand 后,这些都要重新实现。
redux-thunk 的功能,Zustand 原生就支持,直接用 async 函数就行。redux-persist 我们用 zustand-persist 替代,API 差不多,迁移还算顺利。
坑三:类型推导
Redux 的类型推导很复杂,经常要手动写类型。Zustand 的类型推导好很多,但有些复杂的 store 结构,类型还是会有问题。
我们统一了 store 的结构规范,用 TypeScript 的 utility types 简化类型定义,才解决了这个问题。
迁移后的效果
迁移完成后,代码量减少了大概 30%。主要是 Redux 的 boilerplate 代码没了,状态管理逻辑也更清晰了。
性能也有提升。Zustand 的 selector 机制,比 Redux 的 connect 更精确,减少了不必要的重渲染。
现在的状态管理规范
迁移完成后,我们定了几个规范:
- 全局状态用 Zustand,局部状态用 useState。
- 每个功能模块一个 store,不要把所有状态放一个 store 里。
- 复杂的状态逻辑,用自定义 hook 封装。
- 异步操作直接用 async/await,不用 thunk。
如果重新开始
如果重新开始一个项目,我会:
- 一开始就统一状态管理方案,不要混用。
- 优先用局部状态,只有真正需要共享的才用全局状态。
- 选简单好用的方案,不要过度设计。
总结
状态管理混乱是很多项目的通病。一开始没规划好,后面再统一就很麻烦。但统一是必要的,不然维护成本会越来越高。
如果你们项目也有类似问题,建议早点统一。虽然迁移有成本,但长痛不如短痛。选个简单好用的方案,定好规范,严格执行,问题就能解决。
评论区