三个开关,谁说了算

请求失败后,页面还显示成功徽章。先别补第四个布尔值:加载、成功、失败三个开关允许八种组合,其中有些根本不该出现。

布尔组合与合法状态字段对照 / One explicit state avoids contradictory combinations of boolean flags.布尔组合与合法状态字段对照 / One explicit state avoids contradictory combinations of boolean flags.
布尔组合与合法状态字段对照。

这些矛盾会把判断责任推给每个调用者。下一次修改时,人和 agent 都得重新猜哪些组合有效。先列合法状态,再谈用哪种库。

先写请求状态表

本站原创例子只允许四种状态。请先预测:尚未发送请求时,收到成功事件应怎么办?下表约定忽略它,状态保持不变。

当前状态允许事件下一状态
idlesubmitloading
loadingresolve / rejectsuccess / error
errorretryloading
successresetidle

表外事件均不转换。用 discriminated union 把状态名与数据绑定:success 才携带结果,error 才携带错误。reducer 集中处理事件;转换复杂时再考虑状态机。

请求状态与合法转换由 Archify 生成的生命周期图。请求状态与合法转换idleloadingsuccesserrorsubmitresolve表达方式可用 discriminated union、reducer 或状态机。简单场景保持简单。原创示例本站原创请求状态表;表外事件均不转换。success 才携带结果,error 才携带错误。loading → errorrejecterror → loadingretrysuccess → idleresetidle → success× resolve:非法转换忽略事件,保持 idle;测试须拦住提前成功。
合法状态转换与应拦截的转换。

简单请求未必需要专门的库。关键是能回答「现在是什么状态、允许什么事件」,而不是让工具名取代行为说明。

I think I'm addicted to getting agents to build state machines

本站译意:我想我迷上了让 agent 构建状态机。

— Matt Pocock

接口替调用者挡住什么

把请求细节藏进模块时,接口仍要说明成功结果、失败行为与依赖。内部实现由测试约束,调用者不该知道缓存键或重试计数才能使用它。

简单接口包住有价值的复杂度 / A deep module exposes a simple interface and hides useful complexity.简单接口包住有价值的复杂度 / A deep module exposes a simple interface and hides useful complexity.
简单接口包住有价值的复杂度。

深模块的概念来自 John Ousterhout,关注简单接口背后隐藏的有价值复杂度。拆成十个文件或缩成十行,都不能直接证明设计更好。

再检查依赖边界

导入规则可由工具阻止越层访问;领域错误可以建模为明确的类型。Effect 是 Matt 的公开偏好与另一个学习入口,本页不覆盖它,也不建议所有项目迁移。

练习:拦住提前成功

动手练习

为状态表补两份证据

  • 任务:写出 idle 收到 resolve 时的预期,再实现这一约束。
  • 失败检查:该事件不能产生 success,也不能凭空得到结果。
  • 正常检查:submit 后显示加载;reject 后显示错误;retry 后可再次成功。
参考思路

非法事件保持 idle;正常路径依次为 idle、loading、error、loading、success。分别检查状态与用户看到的界面。

完成标准:非法转换测试能拦住提前成功,正常交互也有可重复证据。继续读实现与反馈。历史仓库 xstate-catalogue 已归档,不代表当前 API 示例。

速查

01

状态

一个标签表达当前阶段,数据跟随状态。

02

转换

列允许事件,给表外事件明确策略。

03

接口

调用者依赖行为,测试约束内部实现。

来源

非官方、非商业的学习整理,与 Matt Pocock 无隶属或背书关系。所引作品版权归原作者;MIT 复用内容另附版权与许可声明。