前端状态到底应该放在哪里


前端项目写到一定规模以后,最容易变乱的地方往往不是组件,而是状态。

一开始大家都很克制:一个 useState,一个表单字段,一个弹窗开关。后来页面上多了筛选、分页、弹窗、草稿、权限、缓存、轮询、乐观更新,再回头看,状态已经散在 URL、组件、store、localStorage 和请求库里。真正让人头疼的不是“状态多”,而是每种状态没有明确归属。

我现在更倾向于先问一个问题:这个状态如果刷新页面,应该还在吗?

如果答案是“应该还在”,它大概率不该只放在组件里。比如列表页的搜索词、分页页码、排序方式,这些东西最好进 URL。这样用户刷新、复制链接、返回上一页都符合直觉。URL 不只是路由,它也是一种很朴素的状态容器。

服务端数据则应该交给请求层管理。以前我会习惯性把接口返回塞进全局 store,后来发现这会制造很多隐形问题:缓存什么时候失效、两个页面读到的数据谁更新、失败重试放在哪里。现在如果用 React,我会优先用 TanStack Query 这类工具处理服务端状态。它比手写 store 更适合处理“远端数据不是你真正拥有的”这个事实。

组件本地状态适合放很短命的东西。比如下拉框是否展开、输入框临时值、当前 hover 的行。这些状态离开组件就没有意义,硬塞进全局状态只会让代码更难读。

全局状态我反而用得越来越少。真正适合全局的通常是用户会话、主题、布局偏好、跨页面共享的工作流上下文。它们的共同点是:多个远距离组件需要读写,并且不是服务端缓存能自然解决的东西。

我见过一些项目把 Zustand、Redux 或 Pinia 当成“方便传值”的工具,短期很爽,长期会变成一张看不见的依赖网。组件看起来没有 props,但实际依赖藏在 store 里。改一个字段,影响范围反而更难判断。

一个比较稳的划分是:

  • URL 保存可分享、可恢复的页面状态
  • 请求库保存服务端数据和缓存
  • 组件保存短命交互状态
  • 全局 store 保存真正跨页面的客户端状态

这个划分不是教条。有些后台系统为了效率,会把复杂筛选表单保存在 store 里;有些面向公开访问的页面,会尽量把状态都放进 URL。关键是团队要承认每类状态的生命周期不一样。

前端工程里很多“架构问题”,最后都会落到很小的判断上:这个东西应该活多久,谁拥有它,谁负责让它失效。

想清楚这几个问题,状态管理会少很多玄学。