说说我为什么放弃使用 GetX,转而使用 flutter_bloc + GetIt

写在前面:这不是一篇中立的对比文章,这是一篇事后复盘。我在公司的多个生产项目里深度使用了 GetX,然后花了大量时间在填它挖的坑。如果你正在技术选型,希望这篇文章能帮你少走一些弯路。


一切的开始:GetX 真的很香

说实话,GetX 在我第一次接触 Flutter 时给我留下了极好的印象。

不需要 BuildContext,直接 Get.to() 跳页面;不用写 InheritedWidget,直接 GetxController 管状态;依赖注入?Get.put() 一行搞定。对于一个从其他生态转过来的开发者来说,GetX 简直像是 Flutter 世界里的"万能胶"------把所有烦人的东西都粘在一起,开箱即用,上手极快。

所以我在项目里大量使用了它。Controller 继承 GetxController,页面里 Get.find<XxxLogic>() 随处调用,路由用 Get.toNamed(),弹窗用 Get.dialog(),依赖用 Get.put() 注册......

然后,问题开始慢慢浮出水面。


问题一:Get.find 不是"依赖注入",它是"全局变量换了个马甲"

我项目里有大量这样的代码:

dart 复制代码
class SomeDetailPage extends StatelessWidget {
  final SomeLogic logic = Get.find<SomeLogic>();
  final SomeState state = Get.find<SomeLogic>().state;

  SomeDetailPage({Key? key}) : super(key: key);
  // ...
}

乍一看没问题,但你有没有想过------这个 Get.find<SomeLogic>() 是在 构造函数里 执行的?这意味着在这个 Widget 被实例化的那一刻,SomeLogic 必须已经在 GetX 的全局容器里注册好了。如果没有注册,直接崩溃。

更麻烦的是,你没办法通过构造函数传入一个 mock,这让单元测试和 Widget 测试变得极其痛苦。你没办法孤立地测试这个 Widget,因为它对全局容器有隐式依赖。

真正的依赖注入,是把依赖从外部传进来 。而 Get.find 做的事情,本质上就是一个全局 Map 的查找,只是包了一层类型安全的外壳而已。


问题二:Controller 注册时机是一个隐形的定时炸弹

我在项目里实际遇到了这样的代码,最开始我以为是自己写的有问题,后来才意识到这是 GetX 设计本身带来的:

dart 复制代码
void someMethod() {
  if (Get.isRegistered<AnotherLogic>()) {
    try {
      Get.find<AnotherLogic>().doSomething();
    } catch (e) {
      Future.delayed(const Duration(milliseconds: 100), () {
        if (Get.isRegistered<AnotherLogic>()) {
          Get.find<AnotherLogic>().doSomething();
        }
      });
    }
  }
}

注意看------这里有 isRegistered 检查,有 try-catch,还有 Future.delayed 兜底。为什么会写成这样?

因为 GetX 的 Controller 注册时机和 Widget 生命周期是分离且难以预测的 。当 A 的 onInit 被调用时,B 可能还没注册进去。两个 Controller 之间相互依赖时,你没有一个可靠的方式来保证顺序,只能靠这种"等一会儿再试"的 hack。

这种代码一旦出现,就说明你的架构里有一个无法被类型系统或编译器检测到的隐患------一个随时可能因为时序问题而爆炸的地雷。


GetX 有自己的一套路由管理,Get.back()Get.to()Get.off(),这套 API 背后维护着 GetX 自己的导航栈。

问题在于,Flutter 本身也有一套 Navigator 栈。当你混用了 showDialogshowBottomSheet 这类原生方法,或者使用了某些第三方 UI 库,两套栈就会出现不同步的情况。

最典型的场景:底部弹出一个 BottomSheet,用户点击关闭,调用 Get.back()------结果关掉的不是 BottomSheet,而是后面的页面。因为 GetX 的栈以为当前最顶层是那个页面,而 Flutter 的 Navigator 知道顶层是 BottomSheet。

这类 bug 极难稳定复现,在测试阶段往往发现不了,偏偏在生产环境的某些特定操作路径下必现。而且一旦出现,表现就是页面凭空消失 ,用户一脸懵逼,你看日志也找不到任何异常。 吐槽: 我想你应该能体会到这个问题第一次出现的时候, 查遍了日志和测试人员一起反复的测试都无发复现, 但是生产人员却一直在提这个Bug的感受吗?


问题四:permanent: true 的幽灵

GetX 提供了 permanent 参数,让 Controller 在整个 App 生命周期内不被销毁:

dart 复制代码
Get.put(SomeService(), permanent: true);

这本来是用来处理全局单例服务的。但在实际开发中,这个参数很容易被滥用,或者说------在依赖关系复杂起来之后,你不得不把很多 Controller 标记为 permanent,因为你不知道它会在什么时候被 GetX 自动销毁。

结果就是:一堆"应该随页面销毁"的 Controller 变成了全局常驻对象,它们持有的资源(Stream 订阅、数据库连接、定时器......)永远不会被释放。Crashlytics 上的内存增长曲线会告诉你,你的 App 在连续操作几十分钟后内存占用会不断攀升。

GetX 的自动销毁机制听起来很美好,但它的触发条件是"当没有任何 Widget 依赖这个 Controller 时",这个判断本身在复杂页面嵌套下就很不可靠。


问题五:维护风险

这一点我觉得是最需要认真对待的。

GetX 把路由、状态管理、依赖注入、网络请求、国际化、主题、工具类......几乎所有东西都打包在一个包里。这种"大一统"的设计本身就是一种风险------你对一个生态如此深度绑定。

更重要的是,GetX 从始至终基本上是一个人在维护。不是 Google,不是 Flutter 团队,不是一个活跃的开源社区------是一个人。Issues 堆积,PR 几个月无回应,这在 GitHub 上都是公开可查的事实。

当你的项目依赖于一个可能随时停止维护的库来管理它的路由、状态和依赖注入,你承担的技术债务比你想象的要重得多。


为什么是 flutter_bloc + GetIt?

迁移之后,我选择了这个组合,说说我的理由。

flutter_bloc 的核心优势是可预测性 。每一次状态变化都是显式的 Event → State 流转,你可以在任何时间点知道当前的状态是什么,是怎么来的。Bloc 天然适合单元测试,因为它就是一个接收输入、产生输出的函数,不依赖任何全局状态。bloc_test 提供的 DSL 让测试写起来非常顺手。

GetIt 是一个纯粹的服务定位器(Service Locator),它只做一件事:依赖注入。它不碰路由,不碰状态,就是一个类型安全的全局容器。与 injectable 搭配使用时,可以通过注解自动生成注册代码,极大减少样板代码。最重要的是,GetIt 是一个人们可以放心依赖的、久经考验的库,有大量大型项目在生产中使用。

路由方面我用回了原生 Navigator 2.0 或者 go_router------Flutter 官方出品,跟着 Flutter 一起更新,稳定性有保证。


一些真心话

我不是说 GetX 没有价值, 现在公司多数的APP项目还是在使用它。它降低了 Flutter 的入门门槛,让很多初学者能快速搭起一个能跑的应用,这是实实在在的贡献。

但有一句话我觉得挺有道理:GetX 给了你一把能很快建起房子的电动工具,但这把工具的设计,让你在建的过程中很难检查地基有没有问题。

当项目还小的时候,GetX 的问题都能被"快速开发"的效率掩盖住。等项目大了,屏数多了,逻辑复杂了,那些被掩盖的问题就会以各种奇怪的方式冒出来------路由乱跳、状态不同步、内存上涨、测试无法写......

迁移是痛苦的,但值得。 推荐一个网站: 里面的文章深受启发, 需要翻墙偶😯 medium

相关推荐
Jingyou1 小时前
用 Astro 搭建个人博客:从零到上线的完整实践
前端
吴声子夜歌1 小时前
JavaScript——call()、apply()和bind()
开发语言·前端·javascript
高桥凉介发量惊人2 小时前
质量与交付篇(2/6):CI/CD 实战——自动构建、签名、分发
前端
高桥凉介发量惊人2 小时前
质量与交付篇(3/6):崩溃分析与线上问题回溯机制
前端
angerdream2 小时前
最新版vue3+TypeScript开发入门到实战教程之路由详解三
前端·javascript·vue.js
毕设源码-郭学长2 小时前
【开题答辩全过程】以 基于Web的网上问诊系统的设计与实现为例,包含答辩的问题和答案
前端
酉鬼女又兒2 小时前
零基础快速入门前端DOM 操作核心知识与实战解析(完整汇总版)(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·职场和发展·蓝桥杯·js
喝拿铁写前端3 小时前
一套面向 Web、H5、小程序与 Flutter 的多端一致性技术方案
前端·架构
程序员老刘3 小时前
Flutter版本选择指南:3.41开始进入稳定区间 | 2026年3月
flutter·ai编程·客户端