T
traeai
登录
返回首页
掘金本周最热

GetX 之死 | 8 年从未用过,以后将不会再用

8.5Score
GetX 之死 | 8 年从未用过,以后将不会再用
AI 深度提炼
  • GetX 全生态仓库被作者删除,项目失去源码与社区支持
  • 过度集成导致高耦合,单一依赖失效影响整个应用
  • 隐式依赖和绕过 context 带来可维护性与运行时风险
#Flutter#GetX#状态管理#软件架构#技术选型
打开原文

#### 1.一个时代的句号

2026 年的某一天,当你打开 `https://github.com/jonataslaw/getx`,迎接你的不是那个熟悉的 10000+ star 的仓库,而是一个冰冷的 404 页面。

Image 1: image.png

不只是仓库。作者 jonataslaw(Jonny Borges)的整个 GitHub 主页也 404 了。`get_cli`、`get_server`、`get_storage`——整个 GetX 生态的所有仓库,一夜之间全部消失。

没有告别信,没有迁移公告,没有"项目已归档"的标注。一个曾经是 Flutter 社区最具争议、也最广泛使用的第三方包,就这样无声无息地消失了。

pub.dev 上的 `get` 包还在——因为 pub.dev 是独立托管的,不会因为 GitHub 删库而下架。你的项目今天还能 `flutter pub get`,还能编译,还能运行。但源码没了,issue 没了,786 个未关闭的 issue 没了,78 个待合并的 PR 没了,文档没了。

一个没有源码仓库的包,和一具没有灵魂的躯壳有什么区别?

  • * *

#### 2.回顾:GetX 曾经有多火

在说"为什么不用"之前,先承认一个事实:GetX 确实火过。并且号称宇宙第一:

Image 2: image.png
  • GitHub 10500+ stars,1600+ forks
  • pub.dev 上曾经是 likes 数最高的 Flutter 包之一
  • README 被翻译成十几种语言(日语、韩语、阿拉伯语、葡萄牙语……)
  • 大量教程、视频、课程围绕 GetX 构建
  • 在中文 Flutter 社区尤其流行,几乎每个技术群都有人推荐

它火是有原因的。看这段代码:

// 传统方式:定义 StatefulWidget,写 setState,管理生命周期
class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('$count')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => count++),
        child: Icon(Icons.add),
      ),
    );
  }
}
// GetX 方式:三行搞定
final count = 0.obs;

Obx(() => Text('$count'));

onPressed: () => count++;

对于刚入门 Flutter 的开发者来说,这种简洁性是致命的吸引力。"为什么要写那么多样板代码?GetX 三行就搞定了。"

但简洁和简单不是一回事。简洁是表面上代码少,简单是底层复杂度低。GetX 的代码少,但底层复杂度一点都不低——它只是把复杂度藏起来了。

  • * *

#### 3.我为什么 8 年从未用过 GetX

从 2018 年开始写 Flutter,到现在 8 年了。期间无数次被推荐 GetX,无数次在技术群里看到"用 GetX 三行代码搞定"的安利。但我一次都没用过。甚至我的群聊公告里都写着:

**本群禁止讨论 GetX,有任何问题可以向 GetX 官方提issue。**

不是因为我有先见之明,而是因为它违反了我认为正确的几个原则。

##### 原则一:不要把所有鸡蛋放在一个篮子里

GetX 管了你的状态、路由、依赖注入、国际化、主题、HTTP 请求、本地存储、表单验证……一个包解决所有问题,听起来很美。

但软件工程的基本常识是:**耦合越紧,风险越大。** 来看一个真实的对比:

# 方案 A:每层独立
dependencies:
  provider: ^6.0.0        # 状态管理
  go_router: ^14.0.0      # 路由
  dio: ^5.0.0             # 网络请求
  shared_preferences: ^2.0.0  # 本地存储
  intl: ^0.19.0           # 国际化

# 方案 B:全家桶
dependencies:
  get: ^4.7.3             # 状态 + 路由 + DI + 网络 + 存储 + 国际化 + ...

方案 A 看起来依赖多,但每个依赖都是独立的。`dio` 不维护了?换成 `http` 包,其他四个完全不受影响。路由库想升级?只改路由层,状态管理一行不用动。

方案 B 看起来清爽,只有一个依赖。但这个依赖出问题 = 所有东西出问题。

今天,这个"所有东西出问题"的场景真的发生了。

##### 原则二:隐式依赖是技术债的温床

GetX 最大的卖点之一是"不需要 context"。`Get.find()` 可以在代码的任何角落调用,不需要 Widget 树,不需要构造函数参数。

// GetX 的方式:在任何地方都能拿到任何东西
class OrderController extends GetxController {
  void submitOrder() {
    // 这三个依赖从哪来的?谁注册的?什么时候注册的?生命周期是什么?
    final cart = Get.find<CartController>();
    final auth = Get.find<AuthController>();
    final api = Get.find<ApiService>();
    // ...
  }
}
// 官方推荐的方式:依赖通过构造函数显式传入
class OrderViewModel extends ChangeNotifier {
  final CartRepository _cartRepository;
  final AuthRepository _authRepository;

  // 一眼就知道依赖了什么,测试时直接传 Fake
  OrderViewModel({
    required CartRepository cartRepository,
    required AuthRepository authRepository,
  }) : _cartRepository = cartRepository,
       _authRepository = authRepository;
}

写起来确实多了几行。但代价是什么?

| 维度 | `Get.find()` | 构造函数注入 | | --- | --- | --- | | 知道一个类依赖了什么 | 全局搜索才知道 | 看构造函数就知道 | | 测试时 Mock 依赖 | 配置 GetX 全局容器 | 直接传 Fake 对象 | | 删了一个注册 | 编译通过,运行时崩 | 编译直接报错 | | 重构时追踪依赖 | 全局搜索 `Get.find<T>()` | IDE 的"Find Usages" | | 循环依赖检测 | 运行时才发现 | 编译时就报错 |

这不是"风格偏好",这是**编译时安全 vs 运行时崩溃**的根本差异。一个项目有 50 个 Controller,每个都用 `Get.find()` 拿依赖,你敢重构吗?

##### 原则三:不要依赖个人英雄主义

GetX 本质上是一个人的项目。jonataslaw 一个人写了状态管理、路由、依赖注入、HTTP 客户端、国际化、主题……一个人维护这么大的范围,质量和持续性都无法保证。

早在删库之前,信号就已经很明显了:

  • **2023 年 6 月**:社区开始问"5.0 什么时候发布?"(Issue #2797),至今没有正式版
  • **2023-2024 年**:超过 13 个月没有版本更新,停留在 4.6.5
  • **2025 年 1 月**:有人开了 Issue #3295 直接问"GetX 还活着吗?"
  • **786 个未关闭的 issue**,78 个待合并的 PR——一个人根本处理不过来

对比一下官方推荐的方案:

| 包 | 维护者 | 会因为一个人的决定消失吗? | | --- | --- | --- | | `provider` | Flutter 官方团队 | 不会 | | `go_router` | Flutter 官方团队 | 不会 | | `ChangeNotifier` | Flutter 框架内置 | 不会 | | `Navigator` | Flutter 框架内置 | 不会 | | `flutter_bloc` | 社区团队(多人维护) | 极低概率 | | `riverpod` | Remi Rousselet(provider 作者) | 低概率,且有社区 fork 能力 | | **GetX** | **jonataslaw(一个人)** | **已经发生了** |

##### 原则四:"不需要 context"不是优点,是危险信号

GetX 的宣传语之一是"不需要 context 就能导航、弹对话框、显示 SnackBar"。

// GetX:不需要 context
Get.to(HomePage());
Get.snackbar('标题', '内容');
Get.dialog(AlertDialog(...));

听起来很方便。但 `context` 在 Flutter 中不是累赘——它是 Widget 树的定位器,告诉框架"我在哪里"。绕过 context 意味着绕过 Flutter 的 Widget 生命周期管理,这会导致:

  • SnackBar 在页面已经销毁后还在显示
  • 对话框在错误的路由上弹出
  • 内存泄漏——Controller 没有跟随 Widget 生命周期释放

GetX 用全局状态和静态方法绕过了 Flutter 的设计,短期内写起来爽,长期维护时各种幽灵 Bug 让你怀疑人生。

  • * *

#### 4.给正在用 GetX 的项目的建议

如果你的项目正在用 GetX,不要恐慌,但也不要心存侥幸。

**短期内你是安全的:**

  • pub.dev 上的包不会因为 GitHub 删库而消失
  • 你的 `pubspec.lock` 锁定了版本,`flutter pub get` 还能正常工作
  • 已编译的应用不受任何影响

**但定时炸弹已经埋下了:**

  • Flutter 下一个大版本更新(比如 Dart 4.0)可能导致 GetX 不兼容,没有人会修
  • 发现 Bug 没有人修,发现安全漏洞没有人补
  • 新入职的同事看到一个 404 的仓库链接,会对项目的技术选型产生严重怀疑

##### 迁移路径

不要一次性重写。逐层替换,每次只动一个模块,每次都跑测试:

第一步:路由(影响范围最小,最先动手)
  Get.toNamed('/home')  →  GoRouter 的声明式路由
  Get.back()            →  context.pop()
  GetPage              →  GoRoute

第二步:依赖注入(把隐式改成显式)
  Get.put(MyController())     →  ChangeNotifierProvider(create: (_) => MyViewModel())
  Get.find<MyController>()    →  context.read<MyViewModel>()
  Get.lazyPut(...)            →  Provider 的 lazy 默认行为

第三步:状态管理(工作量最大,收益也最大)
  GetxController + .obs + Obx  →  ChangeNotifier + Consumer / context.watch
  update()                     →  notifyListeners()
  GetBuilder                   →  Consumer

第四步:其他零散功能
  GetConnect        →  dio / http
  GetX 国际化       →  flutter_localizations + ARB 文件
  GetX 主题切换     →  ThemeData + ThemeMode
  Get.snackbar()    →  ScaffoldMessenger.of(context).showSnackBar()
  Get.dialog()      →  showDialog(context: context, ...)

每完成一步,跑一遍测试,确认没有回归。如果你的项目没有测试——这是另一个需要补的债,而且优先级比迁移 GetX 更高。

##### 迁移的工作量评估

| 项目规模 | GetX 使用深度 | 预估迁移时间 | | --- | --- | --- | | 小项目(10-20 个页面) | 只用了状态管理 | 1-2 周 | | 中项目(30-50 个页面) | 状态 + 路由 + DI | 2-4 周 | | 大项目(100+ 个页面) | 全家桶深度使用 | 1-3 个月 |

如果是大项目,建议新功能用新方案写,老功能逐步迁移,不要停下业务开发来专门做迁移。

#### 5.更深层的教训

GetX 之死不是个案。它揭示了开源生态中一个结构性的风险:**社区的繁荣可以掩盖项目的脆弱性。**

10000+ star、几十种语言的 README 翻译、数百个教程视频——这些都是社区繁荣的表现。但项目的健康度不取决于 star 数,而取决于:

  • 核心维护者有几个人?(GetX:1 个)
  • 有没有组织/公司背书?(GetX:没有)
  • issue 响应速度如何?(GetX:786 个未关闭)
  • 版本发布频率如何?(GetX:5.0 难产三年)

下次选择一个第三方包时,不要只看 star 数和 likes。打开 GitHub 仓库,看看:

1. **Contributors 页面**——核心贡献者超过 3 个人吗? 2. **最近的 commit**——最后一次提交是什么时候? 3. **Issue 列表**——维护者有在回复吗? 4. **Release 历史**——版本发布有规律吗?

如果一个包只有一个核心维护者、半年没有 commit、几百个未关闭的 issue——不管它有多少 star,都要三思。

#### 5. 2026 年,Flutter 状态管理该怎么选

GetX 退场后,Flutter 状态管理的格局更清晰了:

| 方案 | 定位 | 适合谁 | 维护状况 | | --- | --- | --- | --- | | `provider` + `ChangeNotifier` | Flutter 官方推荐的入门方案 | 大多数项目 | 官方维护 | | `Riverpod` | provider 的进化版,编译时安全 | 追求类型安全和可测试性的项目 | 社区活跃,多人维护 | | `flutter_bloc` / `Cubit` | 企业级方案,严格的单向数据流 | 大型项目、需要审计追踪的场景 | 社区活跃,多人维护 | | `setState` | 最简单的方式 | 临时状态、原型开发 | 框架内置,永远不会消失 |

没有 GetX。以后也不会有 GetX。

#### 6. 写在最后

8 年前我选择不用 GetX,不是因为预见了今天。是因为几个朴素的判断:

  • 一个人维护的全家桶,风险太高
  • 隐式依赖写起来爽,维护起来痛
  • 绕过框架设计的"便利",迟早要还债

今天这些判断被验证了。但我并不觉得高兴——毕竟有大量的项目和开发者受到了影响。那些用 GetX 写了几万行代码的团队,现在面对的是一个没有源码、没有维护者、没有未来的核心依赖。

如果你正在选择 Flutter 的技术栈,记住一条原则:

**每一层都应该可以独立替换。**

状态管理、路由、依赖注入、网络请求——每一层用独立的方案,每一层都有替代品。这样无论哪一层出问题,你都只需要换那一层,而不是重写整个应用。

这不是过度设计,这是基本的风险管理。

GetX 教会了我们:在开源世界里,**便利是借来的,风险是自己的。**

  • * *

笔者目前正在公众号 **编程之王** 编写并发布《Flutter Agent Skills 全解析》系列,覆盖 Flutter 官方推荐的 22 个开发技能。如果你对 Flutter 官方推荐的架构、状态管理、测试等最佳实践感兴趣,欢迎关注。

问问这篇内容

回答仅基于本篇材料
    0 / 500

    Skill 包

    领域模板,一键产出结构化笔记
    • 论文精读包

      把一篇论文 / 技术博客精读成结构化笔记:问题、方法、实验、批判、延伸阅读。

      • · TL;DR(1 段)
      • · 研究问题与动机
      • · 方法概览
    • 投融资雷达包

      把一条融资 / 创投新闻整理成投资人视角的雷达卡:交易要点、判断、竞争格局、风险、尽调清单。

      • · 交易要点(公司 / 轮次 / 金额 / 投资人 / 估值,材料未明示则写 “未披露”)
      • · 投资 thesis(这家公司为什么值得关注)
      • · 竞争格局与替代方案

    导出到第二大脑

    支持 Notion / Obsidian / Readwise
    下载 Markdown(Obsidian 直接拖入)