Flutter状态先定责

Flutter 项目里很多状态问题,一开始看起来像组件刷新不及时,往深了看却是状态责任没有分清。一个页面同时保存输入框草稿、接口结果、加载状态、路由参数和弹窗开关,短期能跑,后面任何一个需求变动都会牵出一串 setState。
这篇文章不打算把状态责任讲成抽象原则,而是放到真实项目里拆:它受哪些机制影响,哪里需要提前定责,失败以后怎样恢复。我的取舍是,先让状态和责任可解释,再追求漂亮的封装或更快的路径。
先把状态按寿命分类
状态不是一种东西。输入框焦点、页面筛选、接口数据、用户权限、路由参数,它们的寿命不同,拥有者也不该一样。先按寿命分类,比争论用哪种状态管理库更实际。 这一步的价值不是让流程变得复杂,而是把隐含假设摊到桌面上。只要假设能被看见,开发、测试和产品就能围绕同一个边界讨论,而不是在问题出现后各自补解释。
在机制上,Widget 生命周期、局部状态、业务模型、Provider/Bloc、路由参数会一起影响状态责任。它们不是文档里的并列名词,而是会在一次真实操作里互相拖拽:一个环节慢了、断了、被重试了,后面的状态就会跟着变化。
落地时可以先做一个小动作:把状态分成瞬时、页面级、业务级、跨页面四类。 这件事不一定需要大改架构,但它能让问题发生时留下足够线索。很多难查的问题,缺的不是技术能力,而是最开始没有留下可追的痕迹。
这里的反面例子也要写清:所有状态都进全局 store,会让一个小弹窗也影响整条业务链路。 它通常不会在演示环境暴露,因为演示路径短、数据少、角色单一;一旦进入真实用户和长期运行,问题就会变得很难复盘。
我会用一个朴素标准判断这一段是否完成:关闭页面、返回页面、接口失败和重复进入时,状态都能按预期保留或清空。如果团队回答这个问题时还要靠猜,说明状态责任仍然停留在口头规则,没有真正进入实现和验收。
这张图只画和“先把状态按寿命分类”直接相关的路径,重点是让边界、状态和失败出口都能被看见。
局部状态要允许留在局部
有些状态只服务一个组件,比如展开收起、当前 Tab、局部 loading。它们没有必要上升成业务模型,除非其他页面真的要共享。 这一步的价值不是让流程变得复杂,而是把隐含假设摊到桌面上。只要假设能被看见,开发、测试和产品就能围绕同一个边界讨论,而不是在问题出现后各自补解释。
放到“局部状态要允许留在局部”这一节,这些机制不是背景板,Widget 生命周期、局部状态、业务模型、Provider/Bloc、路由参数会一起影响状态责任。它们不是文档里的并列名词,而是会在一次真实操作里互相拖拽:一个环节慢了、断了、被重试了,后面的状态就会跟着变化。
落地时可以先做一个小动作:在组件旁写清这个状态是否需要跨页面共享。 这件事不一定需要大改架构,但它能让问题发生时留下足够线索。很多难查的问题,缺的不是技术能力,而是最开始没有留下可追的痕迹。
这里的反面例子也要写清:为了统一而上升状态,会让代码看起来规范,实际更难删。 它通常不会在演示环境暴露,因为演示路径短、数据少、角色单一;一旦进入真实用户和长期运行,问题就会变得很难复盘。
对“局部状态要允许留在局部”来说,验收标准可以更具体一点:关闭页面、返回页面、接口失败和重复进入时,状态都能按预期保留或清空。如果团队回答这个问题时还要靠猜,说明状态责任仍然停留在口头规则,没有真正进入实现和验收。
针对“局部状态要允许留在局部”,可以把检查清单压成三项:先确认对象是谁,再确认它的生命周期在哪里结束,最后确认失败以后谁负责接手。清单越短,越能逼出真正关键的规则。
业务状态要能被复用
订单、用户、权限、配置这些对象如果被多个页面依赖,就不适合散落在每个 Widget 里。业务状态需要明确来源和刷新策略。 这一步的价值不是让流程变得复杂,而是把隐含假设摊到桌面上。只要假设能被看见,开发、测试和产品就能围绕同一个边界讨论,而不是在问题出现后各自补解释。
放到“业务状态要能被复用”这一节,这些机制不是背景板,Widget 生命周期、局部状态、业务模型、Provider/Bloc、路由参数会一起影响状态责任。它们不是文档里的并列名词,而是会在一次真实操作里互相拖拽:一个环节慢了、断了、被重试了,后面的状态就会跟着变化。
落地时可以先做一个小动作:为业务模型定义加载、刷新、失败和过期规则。 这件事不一定需要大改架构,但它能让问题发生时留下足够线索。很多难查的问题,缺的不是技术能力,而是最开始没有留下可追的痕迹。
这里的反面例子也要写清:每个页面各查一次同一份数据,会造成状态互相覆盖。 它通常不会在演示环境暴露,因为演示路径短、数据少、角色单一;一旦进入真实用户和长期运行,问题就会变得很难复盘。
对“业务状态要能被复用”来说,验收标准可以更具体一点:关闭页面、返回页面、接口失败和重复进入时,状态都能按预期保留或清空。如果团队回答这个问题时还要靠猜,说明状态责任仍然停留在口头规则,没有真正进入实现和验收。
这张图只画和“业务状态要能被复用”直接相关的路径,重点是让边界、状态和失败出口都能被看见。
下面这段只作为边界表达示例,不建议脱离业务直接复制:

  1. enum StateLifetime { instant, page, business, global }
  2. // 先定寿命,再决定放在哪里。

text
路由参数别变成隐形状态
路由参数经常被复制到页面内部,然后慢慢和真实参数不一致。能从路由得到的值,不要在多个地方维护。 这一步的价值不是让流程变得复杂,而是把隐含假设摊到桌面上。只要假设能被看见,开发、测试和产品就能围绕同一个边界讨论,而不是在问题出现后各自补解释。
放到“路由参数别变成隐形状态”这一节,这些机制不是背景板,Widget 生命周期、局部状态、业务模型、Provider/Bloc、路由参数会一起影响状态责任。它们不是文档里的并列名词,而是会在一次真实操作里互相拖拽:一个环节慢了、断了、被重试了,后面的状态就会跟着变化。
落地时可以先做一个小动作:进入页面时只做必要解析,保留原始参数来源。 这件事不一定需要大改架构,但它能让问题发生时留下足够线索。很多难查的问题,缺的不是技术能力,而是最开始没有留下可追的痕迹。
这里的反面例子也要写清:把参数复制到全局状态后,返回路径会变得难以判断。 它通常不会在演示环境暴露,因为演示路径短、数据少、角色单一;一旦进入真实用户和长期运行,问题就会变得很难复盘。
对“路由参数别变成隐形状态”来说,验收标准可以更具体一点:关闭页面、返回页面、接口失败和重复进入时,状态都能按预期保留或清空。如果团队回答这个问题时还要靠猜,说明状态责任仍然停留在口头规则,没有真正进入实现和验收。
针对“路由参数别变成隐形状态”,可以把检查清单压成三项:先确认对象是谁,再确认它的生命周期在哪里结束,最后确认失败以后谁负责接手。清单越短,越能逼出真正关键的规则。
验收要覆盖返回路径
Flutter 状态问题常在返回、重进、弹窗关闭后出现。验收时要按用户真实路径来回走,而不是只刷新首次进入。 这一步的价值不是让流程变得复杂,而是把隐含假设摊到桌面上。只要假设能被看见,开发、测试和产品就能围绕同一个边界讨论,而不是在问题出现后各自补解释。
放到“验收要覆盖返回路径”这一节,这些机制不是背景板,Widget 生命周期、局部状态、业务模型、Provider/Bloc、路由参数会一起影响状态责任。它们不是文档里的并列名词,而是会在一次真实操作里互相拖拽:一个环节慢了、断了、被重试了,后面的状态就会跟着变化。
落地时可以先做一个小动作:测试首次进入、返回保留、重新进入清空和失败重试。 这件事不一定需要大改架构,但它能让问题发生时留下足够线索。很多难查的问题,缺的不是技术能力,而是最开始没有留下可追的痕迹。
这里的反面例子也要写清:只测页面打开成功,状态责任问题不会暴露。 它通常不会在演示环境暴露,因为演示路径短、数据少、角色单一;一旦进入真实用户和长期运行,问题就会变得很难复盘。
对“验收要覆盖返回路径”来说,验收标准可以更具体一点:关闭页面、返回页面、接口失败和重复进入时,状态都能按预期保留或清空。如果团队回答这个问题时还要靠猜,说明状态责任仍然停留在口头规则,没有真正进入实现和验收。
多人维护时怎么不互相踩状态
状态责任最终会落到协作上。一个人写页面时,把状态放在哪里都能记住;三个人同时改筛选、弹窗和缓存时,没有边界的状态会开始互相覆盖。
比较稳的做法是给每类状态写 owner:UI 临时状态归 Widget,业务实体归模型层,跨页面选择归路由或全局上下文。owner 不是为了画架构图,而是为了出问题时知道该从哪里改。
验收时可以故意走一条绕路:进入页面、修改草稿、打开弹窗、返回上一页、再进入。每一步都问状态应该保留还是清空,这比单纯看页面是否渲染成功更接近真实使用。
状态迁移要留下理由
还有一个容易被忽略的点:状态从局部迁到全局,或者从全局退回局部,都应该有理由。没有理由的迁移会让下一位维护者不知道当初为什么这么放,只能继续沿用。
我会在代码评审里追问两个问题:这个状态是否被两个以上页面稳定依赖,页面销毁后它是否还应该存在。两个问题都答不上来,就不要急着把它做成全局状态。
Flutter状态先定责的验收样本
最后还要补一组验收样本。样本不需要覆盖所有情况,但要覆盖正常、异常、边界和恢复。对“Flutter状态先定责”来说,只有这四类样本都能解释,文章里的建议才不是停留在原则层面。
我会特别关注恢复样本:失败后状态是否可追,下一次是否能继续,重复执行是否安全。很多系统不是败在第一次失败,而是败在失败后的第二次处理。
最后用三个问题收住
第一,谁拥有状态责任的最终解释权。没有 owner 的规则,短期靠人记,长期靠运气。
第二,失败以后系统会留下什么证据。证据不是越多越好,而是要能回答:发生在什么条件下、处理到哪一步、下一次应该从哪里恢复。
第三,这个方案适合哪些场景、不适合哪些场景。状态集中方便调试,但过度集中会让局部交互也变成全局负担;状态分散灵活,却需要清晰的所有者。,所以不要把一次有效实践包装成万能答案。
如果这三个问题能说清,状态责任就不再只是一个技术点,而会变成团队协作中的稳定边界。后续无论换框架、换人员还是换业务规模,都能沿着这条边界继续调整。