关于应用状态管理的最流行建议也是最不实用的建议:选择一个全局存储并将所有内容都放在其中。这种方法将__CAPGO_KEEP_0__响应、模态可见性、未保存的表单输入、身份验证和导航过滤器视为具有相同的生命周期。它们并没有。 应用状态管理 is also the least useful: pick one global store and put everything in it. That approach treats API responses, modal visibility, unsaved form input, authentication, and navigation filters as if they had the same lifecycle. They don’t. A Capacitor or Electron application runs across a web layer and native or desktop runtime, so state must be separated by 应用程序跨越一个web层和native或桌面运行时,因此状态必须根据它来自哪里、应该存活多久、谁拥有它以及当网络或进程消失时发生什么而分离。.
状态管理成为基础,因为移动运行时是易失性的。应用程序可以在旋转变化或低内存条件下被销毁并重新创建,Android提供了实例状态恢复和生命周期回调,如 onSaveInstanceState() 因此,正如在一个 可靠的移动应用程序状态管理的UC河滨研究院的研究中所documented 。同一研究检查了966个应用程序和4,808个活动 ,发现有452个应用程序,约46.8%,包含至少一个非空活动,而 1,896 个活动 总体而言,所有活动都有非空状态。状态不是在 UI 工作后添加的可选抽象。它是运行时契约的一部分。
目录
重新思考全局存储模型

Redux 与 Context 与 MobX 是错误的起点。一个存储可以协调更新,但它无法确定一个值是否属于服务器、当前屏幕、表单工作流或地址栏。将所有值放入一个全局容器中会导致重复的 API 缓存、过期的 URL 参数和订阅使无关屏幕重新渲染。
一个可靠的设计为每个值分配一个拥有者和恢复策略:
- 服务器状态 属于数据获取和缓存层。 API 响应、加载状态、错误、新鲜度、失效和重试都描述了一个远程资源。服务器保持权威性,因此客户端不应维持第二个永久的真实来源。
- 客户端或 UI 状态 位于组件或特征的附近。模态可见性、选中的标签页、展开的行、主题偏好和临时交互标志通常不需要应用级别的持久性。
- 表单状态 遵循一个单独的工作流。草稿输入、验证消息、脏状态和提交进度可能需要在表单内导航时生存,但它们不应自动成为共享业务状态。
- URL 状态 属于路由器。搜索项、过滤器、排序选项、选中的记录和地址栏中的分页可以被书签、分享、恢复和检查而不需要将它们复制到另一个存储中。
为什么一个存储会导致架构拖累
2024 应用状态管理的详细评述 探讨了Web和移动应用程序的状态管理。其覆盖范围反映了从散布的UI变量向明确的、生命周期感知的架构的实用转变。Android从实例状态包向ViewModel和SavedStateHandle的进展遵循了相同的方向,但这并不意味着每个值都应放在一个共享容器中。
全局存储仍然有一个定义的作用。会话标识、权限、应用级首选项、连接策略和谨慎限定跨功能工作流可能需要共享所有权。问题开始时,当存储变成一个值的垃圾场时,这些值更适合拥有者。
实用规则: 如果一个值可以从服务器、URL或当前组件重构,除非有特定的工作流需要,否则将其从全局状态中排除。
这个界限也支持独立拥有模块。将产品分解为可部署区域的团队可以应用在 微前端架构, rather than building one dependency graph across the application. In a Capacitor or Electron codebase, a hybrid model works better: remote data uses cache and synchronization rules, UI values remain local, and durable workflow state gets explicit persistence. That separation limits competing writers and makes recovery after reloads, suspended processes, or offline periods easier to reason about.
跨平台应用程序的架构模式
一旦状态有了拥有者,实现选择就变得非常狭窄。 大多数客户端状态都符合以下三个模式之一: 模式一:模式二: 模式三:模式 复杂度 内存占用
| 最佳使用场景 | 局部组件状态 | 小型共享存储 | 事件驱动通信 |
|---|---|---|---|
| 隔离模块之间的通信 | 低 | 低 | 屏幕特定开关、草稿、披露面板、临时选择 |
| 集中式轻量级存储 | 中等 | 中等 | 共享会话首选项、主题、活跃工作区、跨功能UI协调 |
| 事件总线架构 | 中等到高 | 可变 | 松散耦合模块、插件通知、原生桥事件、隔离功能边界 |
本地状态应是默认值
A组件本地值具有短的依赖路径。当模态窗口打开、行展开或表单字段发生变化时,拥有该特性的功能可以更新而不通知整个应用。这减少了意外耦合并使单元测试直接。它还限制了在移动屏幕被挂起或桌面窗口保持打开的长会话期间保留的内存。
当多个远程功能需要相同的值或工作流程跨越路由边界时,局部状态会变得不方便。在这种情况下,提升状态到特性商店通常比将其放在应用级单例中更好。一个小的商店,如 Zustand 或 Pinia,可以公开关注的选择器和显式动作而不需要每个组件都订阅每个变化。
这种权衡是纪律。轻量级商店容易创建,因此团队可能会创建许多重叠的商店并且拥有权利不明确。请为所有者命名,定义变异方法,并避免暴露任何组件都可以重写的可变对象。
事件很有用,但它们不是数据库
事件总线适用于通知,如“原生分享完成”、“窗口变为活动”或“后台任务接收新数据”。它有助于插件和模块之间的通信而不需要相互导入。然而,它作为业务状态唯一记录的工作并不理想,因为事件是暂时的。一个被挂起、卸载或注册晚的订阅者可能会错过消息。
使用事件来宣布某件事情发生了,然后让接收模块查询其权威存储或数据层。保持事件名称狭窄,payload版本化,特别是native和webcode可能会各自演进。
对于更广泛的架构背景, 来自Bridge Global的移动应用开发见解 在权衡共享code和平台特定行为时,很有用。同样的边界适用于应用状态:在稳定时共享域规则,但隔离生命周期适配器和native集成点。一个实际的 移动应用架构 应该在文件结构和依赖图中使这些边界可见。
持久性和离线首先同步
内存存储不是持久状态。操作系统可以暂停或终止一个移动进程,桌面用户可以关闭一个窗口,网络可以在一个突变在飞行时消失。离线首先设计从开始决定用户必须能够恢复什么,然后选择存储和同步规则围绕这个要求。
构建一个可靠的写入路径
可靠的流程将用户体验与远程确认分开:
- 先写入本地。 将用户操作应用到本地数据库或持久文档存储中,使界面响应而不等待网络。
- 排队变更。 存储一个包含实体标识符、操作类型、数据载荷、创建上下文和重试状态的操作。仅在内存中存储的队列在进程结束时会消失。
- 呈现真实状态。 区分本地保存、待同步、已同步和失败的状态。用户需要知道是否在设备上或远程确认了更改。
- 在条件允许时进行同步。 重新连接监听器、应用程序前台事件和预定后台工作可以触发重试。同步工作者应该是幂等的,因为中断的请求可能会再次发送。
- 故意解决冲突。 服务器响应必须确定本地操作是否被接受、拒绝、合并或需要用户审查。
SQLite 是一种实用的选择,适用于关系记录和事务队列的本机应用程序。IndexedDB 可以适用于浏览器定向存储和 Electron 渲染器 code,假设团队处理了模式升级和事务边界的处理。保持序列化明确。持久化域数据和恢复元数据,而不是组件实例、闭包或指向本机对象的引用。

冲突策略是产品决策
最后写入者获胜的方法简单,但可能丢弃合法的编辑。字段级合并在独立字段可以安全合并时有效。领域特定规则对于库存、审批、财务记录或临床工作流来说更安全,因为自动合并可能改变含义。某些冲突应该阻止同步并要求用户选择。
同步层也应该将 读取重建 与 变更回放分开。重新获取服务器数据并不能证明一个排队的变更成功了,回放一个变更也不能保证最终的记录仍然与当前服务器的表示相符。记录服务器版本或等效的验证器,返回结构化的冲突响应,并保留足够的队列历史来解释失败。
对于用户界面部分的这个设计来说, 在Vue、Angular或React中创建一个离线屏幕 提供了一个有用的界面关注点:离线模式应该是可见的应用状态,而不是在控制台中隐藏的异常。
以下视频可以补充离线行为和同步的实现工作:
性能优化和测试策略
状态性能问题很少源自一个慢的减少函数。它们是由广泛的订阅导致的,导致无关组件渲染,派生值不必要地重新计算,大的对象图保持引用,或者同步工作在UI线程上运行。测量更新传播而不是猜测哪个库是最快的。
A 2026 年的benchmark使用一个仪表盘 100 个连接组件和 10,000 次迭代 测量MobX在 0.3 ms用于简单的变化,0.4 ms用于嵌套的变化,0.6 ms用于派生变化,而Redux Toolkit测量 0.8 ms,1.2 ms,1.5 ms 在相应的测试中。同样的benchmark记录了 Zustand的2.8 MB和Redux Toolkit的4.2 MB 在其内存比较中。这些是benchmark特有的观察结果,而不是普遍的生产保证,但它们说明了订阅粒度和更新策略的重要性。查看 比较的React状态管理benchmark 测试环境的详细信息。
调整更新边界
从选择器开始。一个组件应该订阅最小的有意义的切片,而不是整个会话对象或API响应。 当计算成本高昂时,保留派生数据,但不要通过本能来保留每个原始值。 memoization 还会增加保留和比较的工作量,因此在添加和删除 memoization 之前,务必进行 profiling。
虚拟化长列表,规范记录更新时针对单个实体,避免替换大型根对象仅因为小字段发生了变化。在 Electron 中,监视渲染器内存长时间运行,因为窗口可能会保持活跃很长时间,远远超过移动屏幕。在 Capacitor 中,避免在输入爆发期间进行同步持久化。 在关键时刻,缓冲草稿或持久化有意义的检查点,同时确保崩溃不会丢失产品承诺保留的数据。
一项 Springer 链接研究发现,改变状态管理方法可以在测试场景中平均减少程序执行时间 17% , 支持了减少不必要的同步和重计算可以产生实质性的运行时收益的想法。 结果出现在 web 应用程序状态管理性能研究 , 并且不应被视为每个堆栈都能保证的改进。
测试过渡,而不是仅仅测试值。
一个状态测试检查 isLoading 在成功请求后
- 会错过危险路径。 测试序列如下: 水化:
- 中断: 请求被取消或应用在变异期间后台运行。
- 重放: 安全重试并且不重复服务器效果的排队操作。
- 冲突: 服务器拒绝过时版本,UI显示可恢复的解决方案。
- 隔离: 本地UI更新不会导致其他功能渲染或变异。
在网络边界使用模拟服务器状态适配器,然后使用完整工作流程的集成测试。开发和CI中添加仪表板以检测意外订阅计数、无界队列和在特性卸载后发生的状态转换。最有价值的测试fixture通常是现实的生命周期序列,而不是另一个孤立的减少器断言。使用 应用性能优化指南 将这些测量转换为可重复的发布检查时。
平台特定指南:Capacitor和Electron
A web application can assume that its JavaScript process remains available longer than a mobile app can. Capacitor 和 Electron remove that assumption in different ways. Capacitor places the web layer inside a mobile lifecycle managed by iOS or Android, while Electron keeps a Chromium renderer connected to a desktop process model with windows that can appear and disappear independently.

Capacitor 需要生命周期感知的水合
将后台作为检查点机会,而不是证明进程将恢复的证据。在应用状态发生变化时,清除关键的待处理变异,记录当前的同步游标,并释放不应保持活动的资源。在前台,重新水合丢失的内容,验证身份,刷新陈旧的服务器数据,并在本地存储准备好后重新启动订阅。
JavaScript存储 shouldn’t 直接拥有native插件内部。摄像头会话、生物识别提示、推送注册、文件系统句柄和后台任务各有平台生命周期规则。 Wrap them in adapters that translate native callbacks into domain events or commands. The store can then represent states such as unavailable, requesting, active, failed, or completed without retaining a native object that becomes invalid after suspension.
A webview boundary also makes serialization important. Pass plain data across the Capacitor bridge, validate plugin responses, and version messages when a live update may leave different web bundles interacting with installed native code. 如何Capacitor桥接web和nativecode __CAPGO_KEEP_0__解释了使得此适配器方法必要的边界。
Electron需要进程拥有权
Electron的主进程应该拥有特权操作和持久的协调,而渲染器拥有视图特定的状态。使用类型化IPC命令来执行操作,如读取安全设置、写入文件或协调窗口。不要将广泛的文件系统访问暴露给每个渲染器,并且不要将一个窗口接收到的事件视为持久的应用程序记录。
多窗口应用程序需要显式的同步模型。主进程可以分发权威的更新,而每个渲染器维护本地的呈现状态。如果两个窗口编辑相同的记录,应用程序需要版本检查或冲突策略,而不是简单地广播事件。一个关闭的窗口必须能够在重新打开时重构状态,因此主进程或持久层必须保持可恢复的数据源。
Capacitor和Electron可以共享领域模型、API客户端、队列格式和减少器。它们不应该共享生命周期code。最强大的跨平台架构有一个共同的状态词汇和平台特定的持久性、桥接和恢复适配器。
Migrate to Modern Hybrid Architecture
Legacy Global Store Rarely Needs Rewrite

Start with Ownership, Not Technology
Create a state catalogue for the existing store. For every field, record its source, consumers, mutation paths, persistence requirement, and recovery behavior. Mark whether it is server-derived, route-derived, form-owned, feature-local, or shared. This exercise often reveals that the global store contains several unrelated systems hidden behind one API.
Move server state first. Replace manually mirrored API fields with a dedicated fetching and caching layer that owns request status, invalidation, retries, and revalidation. Keep selectors temporarily compatible so existing screens can migrate without changing every call site at once. Delete the duplicated server copy only after the new source has passed integration tests.
Next, Return URL State to Router
逐步提取功能状态
将模态状态、向导进度和本地选择移到最近的功能边界。如果多个组件需要该值,请使用具有窄接口的功能存储。将剩余的全局存储保留用于横切关注点,如会话策略、主题、权限或显式共享的工作流。
在过渡期间使用兼容层。它可以从新源读取,同时暴露旧选择器形状,允许在标志下迁移功能。独立释放每个提取,监控错误路径和hydration行为,直到新所有权模型证明稳定为止保留回滚路线。
对于Capacitor和Electron团队,Capgo可以通过目标通道传递签名的JavaScript、CSS、配置和资产包,具有回滚控制、版本历史、设备日志、采用和失败指标以及自动回滚保护。这样就可以逐步部署状态管理重构,而原生变化仍然遵循相关平台发布过程。操作性好处是对迁移的控制,而不是跳过测试或兼容性规划的理由。
最佳实践和避免的常见错误
状态架构失败时,审查问题保持模糊。使用以下检查在设计审查和拉取请求中:
- 在code中命名所有者: 问,“哪个模块可以改变这个值,什么 API 确保了边界?”拒绝仅因为两个组件当前需要相同字段而添加的存储。
- 定义恢复协议: 对于每个持久化值,记录是否在重新加载、应用程序重启、注销和帐户切换时保留或清除它。草稿发票可能会在重启后存活,而选择的工作区可能需要重新验证。
- 使同步可观察: 记录请求 ID、版本号、重试次数和冲突结果。设置重复重试或冲突失败的警告,用户报告丢失更新之前。
- 审查命令,而不是对象形状: 一个 mutation 应该说明其商业意图、验证输入并暴露审计友好的动作名称。绕过这些检查的直接写入属于审查讨论。
- 测试恶意序列: 运行测试,包括导航期间的水化竞争、中断写入后重试、重复交付、两个窗口的离线编辑和注销期间的挂起请求。
- 检查移除路径: 如果一个旧的选择器、持久化适配器或事件监听器仍然写入旧的存储,一个迁移是不完整的。添加一个测试,失败时两个源都可以更新相同字段。
一个实际的 PR 问题是,“如果这个过程在写入开始后消失,但在确认之前,会发生什么?”答案应该指出持久化数据、重试所有权、去重复和用户可见的失败状态。
Capgo 帮助 CapacitorJS 和 Electron 团队通过目标通道将 JavaScript、CSS、配置和资产更新分发给设备,带有签名包、滚动控制、设备级日志和回滚保护。使用 Capgo 用于向 CapacitorJS 和 Electron 团队分发 JavaScript、CSS、配置和资产更新,带有签名包、滚动控制、设备级日志和回滚保护。