按生命周期查看作用域状态:何时React Portal真正需要全局存储
了解如何根据状态的所有者和生命周期来判断 React Portal 中的状态应存放何处,为何全局状态管理会引发清理问题,以及在哪些情况下真正需要使用 Redux 或 Zustand。
构建内部门户的团队往往以“Redux还是Zustand?”作为状态讨论的起点。一个更有用的首要问题是:究竟哪些状态真的需要具备全局性?本文将介绍如何根据状态的拥有者及其有效期对门户状态进行分类,解释为何将短期有效的状态存放在全局存储中会不断引发清理错误,以及何时应该使用全局库。
大多数门户状态都是短期的
门户通常由多个独立的工作流组成。用户打开一个页面,进行搜索或编辑操作,完成任务后便转向应用的另一个区域。表单值、筛选条件、选中的表格行、当前激活的标签页以及模态框是否打开,这些都属于该页面或工作流的范围,很少需要在不同且无关的路由之间保持一致性。
只有少数数据才是真正适用于整个应用范围的:
- 已登录的用户;
- 认证状态与权限;
- 当前所在的组织或租户;
- 主题与语言偏好设置。
几乎所有其他信息都应与其所属的功能模块保持关联。
全局状态管理将生命周期问题转化为清理任务
若将临时状态存储在 Redux、Zustand、MobX 或其他全局状态管理工具中,这些状态的生命周期将超过创建它们的页面的生命周期。此时就需要额外的代码来在用户导航、提交表单、登出、更换租户以及任何其他离开当前页面的场合重置这些状态。一旦遗漏了某个场景,用户返回页面时就会看到过时的筛选条件、之前的选择内容或上次访问时未填完的表单信息。由于这类问题取决于用户访问页面的具体顺序,因此很难复现。
相关的经验法则很简单:如果全局状态总是需要被清空才能表现得像局部状态,那它或许从一开始就该是局部状态。
选择最合适的最小作用域
将每部分状态与能够覆盖其整个生命周期的最合适工具对应起来:
- 针对单个组件的 UI 行为,使用组件状态;
- 对于需要在某个功能模块或一组相关路由之间共享的状态,使用 React Context;
- 过滤、分页及其他导航状态可使用 URL 查询参数,这还能让视图可共享并在刷新后依然保留;
- 从后端获取的数据则应使用 TanStack Query 等服务器状态库,因为缓存与数据失效处理是它的职责而非存储库的职责;
- 仅对于真正需要在整个应用中使用的客户端状态,才使用全局存储库。
路由级上下文值得特别提及。当你在一组路由周围放置提供者时,若不挂载该组路由,提供者将会被卸载,其状态也会自动消失。React的组件生命周期会自动完成原本需要手动处理的清理工作。如果你选择使用状态管理库的真实原因是需要在多层组件间传递属性,那这是一个可以通过更简单的方法解决的独立问题,相关内容可见为何属性层层传递并非安装Redux或Zustand的理由。
何时需要使用全局状态管理库
当状态确实需要在不同页面之间持续存在时,Redux、Zustand及类似库才是合适的选择。典型应用场景包括:
- 购物车功能;
- 涉及多页面的复杂结账流程;
- 需要持久保存的草稿内容。
- 以离线优先为设计原则的应用程序;
- 覆盖整个应用的消息或通知系统;
- 撤销与重做功能;
- 需要协调客户端状态的实时功能;
- 包含众多相互依赖的状态转换的复杂工作流。
在这些情况下,集中式状态管理能够解决实际问题,而非制造新问题。
关键要点
- 在选择任何库之前,先确定状态的归属及生命周期。
- 属于某个工作流的状态应随该工作流的结束而消失,理想情况下是通过卸载而非手动重置来实现。
- 将服务器数据存储在查询库中,将导航状态放在URL中。
- 为整个应用需要长期共享的状态预留全局存储空间;知道何时不应使用此类存储也是良好架构的一部分。