用 MobX 实现无 StatefulWidget 的计数器:flutter-examples 的 statless_counter_app 深度解析
发布时间:2026/10/15 16:00:35 锦皓数字建站

示例工程移动开发【免费下载链接】flutter-examples[Examples] Simple basic isolated apps, for budding flutter devs.项目地址https://gitcode.com/gh_mirrors/fl/flutter-examples点击查看免费下载本文以 flutter-examples 仓库中的 statless_counter_app 示例为核心讲解如何在完全不使用 StatefulWidget 的前提下借助 MobX 状态管理库完成一个可交互、可自动刷新的计数器应用。读完本文你将掌握 MobX 的observable/action注解、代码生成工具 build_runner 的用法以及Observer组件与 StatelessWidget 的配合模式并能直接套用到自己的 Flutter 项目中。一、示例概述为什么叫 statless无状态计数器仓库中的该示例在其 README 中只有一句话点题This app is a counter app using a State Manager Mobx. So we have implemented a counter app without a Stateful Widget.这句话揭示了本示例的核心思路计数器的可变状态不再由 Widget 自身持有而是被提升到了独立的 MobX Store 中。UI 组件全部使用StatelessWidget只负责读取 Store 数据并触发action从而把界面与状态彻底解耦。这种做法的实际意义在于Widget 树中不再出现setState与State生命周期构建函数保持纯净计数器状态可以脱离 Widget 存在便于单元测试、跨页面共享以及后续接入 Provider / GetIt 等依赖注入方案界面刷新由 MobX 的响应式机制按需触发而不是整棵子树重建。下面结合 lib/main.dart、lib/counter.dart、lib/counter.g.dart 与 test/widget_test.dart 逐一拆解实现细节。二、依赖配置MobX 全家桶与代码生成工具链示例的依赖声明位于 pubspec.yaml核心部分如下environment: sdk: 2.12.0 3.0.0 dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 mobx: ^2.0.0 flutter_mobx: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter build_runner: ^1.12.2 mobx_codegen: ^2.0.0需要说明的要点mobx核心响应式状态管理库提供Observable、Action、Atom、Reaction等底层原语flutter_mobxFlutter 绑定层提供Observer组件用于在 Widget 树中订阅 Store 变化mobx_codegenbuild_runner代码生成工具链。MobX 2.x 在 Dart 上借助observable/action注解配合代码生成而非依赖运行时反射mobx_codegen负责扫描注解并生成对应的 Store 实现build_runner则是生成任务的执行入口。Dart SDK 约束为2.12.0 3.0.0即本示例面向空安全null safety时代的 Dart 2.12 版本mobx、flutter_mobx、mobx_codegen均锁定 2.x 主版本与示例所用 API 保持兼容。若在更新的环境中复现建议将依赖升级到最新的 mobx 4.x / flutter_mobx 4.x 并执行flutter pub getAPI 形态基本一致。三、状态层用observable与action定义 Counter Store状态存储类定义在 lib/counter.dart全文如下import package:mobx/mobx.dart; // Include generated file part counter.g.dart; // This is the class used by rest of your codebase class Counter _Counter with _$Counter; // The store-class abstract class _Counter with Store { observable int value 0; action void increment() { value; } }这段代码展示了 MobX 2.x 在 Dart 中的标准 Store 编写范式可拆解为四个部分part counter.g.dart;声明本文件与生成的代码文件属于同一个 library。counter.g.dart由mobx_codegen生成必须存在且保持最新否则编译失败。class Counter _Counter with _$Counter;将抽象基类_Counter存放业务字段与方法与生成的混入_$Counter存放响应式实现组合成对外可用的具体类Counter。这是 MobX 官方推荐的 mixin 模式目的是把业务代码与样板代码分开。observable int value 0;把value标记为可观察状态初始值为 0。MobX 会拦截对该字段的读写在读取时建立依赖记录、在写入时触发变更通知。action void increment() { value; }把自增逻辑封装为 action。Action 的意义在于把对多个可观察字段的批量修改包装成一个原子事务action 执行期间收集所有变更结束后统一对外广播一次避免 UI 被中间态频繁打断。从源码结构看示例刻意保持 Store 极简——只有一个int value与一个increment()恰好覆盖了 MobX 状态管理的两个核心概念可观察数据 行为修改非常适合作为入门样板。四、生成代码counter.g.dart里的响应式底层原理mobx_codegen会依据counter.dart生成 lib/counter.g.dart。该文件顶部明确标注GENERATED CODE - DO NOT MODIFY BY HAND内容是一个名为_$Counter的 mixin其中封装了 MobX 运行时最核心的两个机制1. Atom可观察字段的读写拦截final _$valueAtom Atom(name: _Counter.value); override int get value { _$valueAtom.reportRead(); return super.value; } override set value(int value) { _$valueAtom.reportWrite(value, super.value, () { super.value value; }); }reportRead()在 getter 被调用时登记当前正在执行的表达式依赖了value这是 MobX 建立依赖追踪的入口reportWrite(newValue, oldValue, setter)在 setter 被调用时检查新旧值若发生变化则执行真正的赋值并通知所有依赖value的Reaction重新执行。2. ActionControlleraction 的运行时包装final _$_CounterActionController ActionController(name: _Counter); override void increment() { final _$actionInfo _$_CounterActionController.startAction(name: _Counter.increment); try { return super.increment(); } finally { _$_CounterActionController.endAction(_$actionInfo); } }生成的increment()会先startAction开启一个 action 上下文再调用业务方法体super.increment()即_Counter中的value最后在finally中endAction结束并触发变更广播。这样即使业务代码中途抛异常action 状态也能被正确回收。3. 重新生成代码的命令由于counter.g.dart是生成产物修改counter.dart后必须重新运行生成器flutter pub get flutter pub run build_runner build --delete-conflicting-outputs--delete-conflicting-outputs用于在旧生成文件与新生成文件冲突时以新为准避免构建卡死。若需要监听文件变更并自动重新生成可将build替换为watchflutter pub run build_runner watch五、UI 层StatelessWidget Observer 的无状态界面入口文件 lib/main.dart 中整个界面由两个StatelessWidget组成MyApp与MyHomePage。1. Store 的实例化与注入方式final counter Counter(); // Instantiate the store void main() runApp(MyApp());示例采用最简单的方式在文件顶层直接final counter Counter();全局实例化 Store随后在build方法中直接引用。对于单个页面的演示足够直观在真实多页面项目中通常还会配合Provider、GetIt或InheritedWidget将 Store 注入 Widget 树但核心思路一致——Store 与 Widget 分离UI 只消费状态。2. Observer精确订阅局部刷新MyHomePage的build中计数器文本被包在Observer里// Wrapping in the Observer will automatically re-render on changes to counter.value Observer( builder: (_) Text( ${counter.value}, style: Theme.of(context).textTheme.headline4, ), ),关键点在于Observer的builder内如果读取了counter.valueMobX 会自动把这个 builder 注册为对value的依赖当value变化时只有该Observer对应的构建逻辑被重新执行而不是整个MyHomePage重建——这与setState重建整个State.build形成鲜明对比在状态字段多、页面结构复杂时能明显缩小重建范围StatelessWidget因此完全可以承担交互逻辑它不再依赖setState状态更新的发动机在 Store 中。3. 触发行为按钮直接调用 actionfloatingActionButton: FloatingActionButton( onPressed: counter.increment, tooltip: Increment, child: Icon(Icons.add), ),onPressed直接传入counter.increment方法引用。点击按钮 → 调用 action →value→ Atom 通知 → Observer 重绘文本形成一条完整的响应式数据流全程没有任何setState。AppBar标题MobX Stateless Widget Counter也再次点明本示例的主题用 MobX 写无 StatefulWidget 的计数器。MyApp本身也沿用了无状态写法MaterialApp中配置了title: MobX、debugShowCheckedModeBanner: false以及primarySwatch: Colors.blue的主题说明即使应用根节点同样可以保持为 StatelessWidget。六、测试验证无状态计数器同样可测示例自带冒烟测试 test/widget_test.dart完整覆盖了初始为 0、点击自增为 1的核心行为testWidgets(Counter increments smoke test, (WidgetTester tester) async { await tester.pumpWidget(MyApp()); // Verify that our counter starts at 0. expect(find.text(0), findsOneWidget); expect(find.text(1), findsNothing); // Tap the icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text(0), findsNothing); expect(find.text(1), findsOneWidget); });这段测试从另一个角度印证了架构的收益由于状态与 UI 分离测试只需关注界面上的文本是否符合预期而状态本身的逻辑value的初值与increment的行为也可以脱离 Widget 直接编写纯 Dart 单元测试。执行测试的命令为flutter test七、运行方式与上手步骤在仓库根目录下的statless_counter_app/子目录中运行cd statless_counter_app flutter pub get flutter pub run build_runner build --delete-conflicting-outputs flutter runflutter pub get安装 pubspec.yaml 中声明的依赖build_runner build确保 counter.g.dart 与 Store 定义一致flutter run启动应用点击右下角悬浮按钮即可看到数字自增且标题栏与 UI 全程由 StatelessWidget 渲染。八、延伸思考MobX 无状态模式的适用场景从本示例可以延伸出两条对实战有指导意义的结论何时适合用 MobX 替代 StatefulWidget当页面存在多个相互关联的可观察字段、状态需要跨页面共享、或希望将业务逻辑从 Widget 中抽出做独立测试时MobX 的 Store Observer 模式是比手写setState更可控的方案而仅限单个 Widget 内部的简单临时状态直接用StatefulWidget反而更轻量。从 Stateless 到无状态架构的层次关系本示例展示的是Widget 无状态全部为StatelessWidget而非应用无状态——真正的状态被集中到了CounterStore。这种UI 无状态、状态有归属的分层与 Redux、Provider ChangeNotifier 等方案的出发点一致只是 MobX 用可观察对象和自动依赖追踪把样板代码降到了最低。如果想要继续深入可以依次阅读 lib/main.dart 与 lib/counter.dart 的注释式实现对比 lib/counter.g.dart 理解生成代码的职责再动手为Counter增加decrement()、reset()等 action观察 Observer 的局部刷新行为——这将是理解 Flutter 响应式状态管理的最佳练习起点。赞分享示例工程移动开发【免费下载链接】flutter-examples[Examples] Simple basic isolated apps, for budding flutter devs.项目地址https://gitcode.com/gh_mirrors/fl/flutter-examples点击查看免费下载相关推荐Flutter应用退出时取消Dio请求的完整指南StatefulWidget实现方案Flutter应用退出时取消Dio请求的完整指南StatefulWidget实现方案 在Flutter应用开发中Dio作为强大的HTTP客户端库被广泛使用网络后端WebSocketFlutter Bottom Sheet 实战flutter-examples 中 showModalBottomSheet 的完整实现与源码解析Flutter Bottom Sheet 实战flutter examples 中 showModalBottomSheet 的完整实现与源码解析 本指南以示例工程移动开发Serverless Examples项目深度解析无服务器架构的宝库Serverless Examples项目深度解析无服务器架构的宝库 Serverless Examples项目是一个由Serverless Framewor示例工程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。