一、GetX 是什么?
GetX 是 Flutter 生态中的一个 全家桶式框架,它不只是一个状态管理方案,而是把状态管理、路由导航、依赖注入三件事打包在了一起。
很多人第一次接触 GetX 的感受是:"怎么什么都能做?" —— 这既是它的优势,也是它的争议所在。
GetX 的三大核心模块
GetX
├── 状态管理(State Management) ── 替代 setState / Provider / Bloc
├── 路由管理(Route Management) ── 替代 Navigator 系列 API
└── 依赖注入(Dependency Injection)── 替代 Provider / get_it
为什么这么多人用?
一个字:简单。
用 Bloc 写一个计数器功能,你需要:Event 类 + State 类 + Bloc 类 + BlocProvider + BlocBuilder,至少 4~5 个文件。
用 GetX 写同样的功能:一个 Controller 类 + Obx(() => Text()),两行搞定。
二、状态管理
2.1 两种响应式风格
GetX 提供了两种状态管理方式,理解它们的区别是用好 GetX 的第一步。
简单状态管理(GetBuilder)
- 手动调用
update()通知刷新 - 类似
setState(),但作用域更小 - 性能好,适合不需要频繁自动响应的场景
工作原理很朴素:Controller 内部维护一个监听者列表,调用 update() 时遍历通知所有 GetBuilder Widget 重建。本质就是一个 手动版的观察者模式。
响应式状态管理(Obx)
- 变量用
.obs标记,变化时自动触发 UI 刷新 - 不需要手动调用
update() - 类似 Vue 的响应式、MobX 的 observable
// 声明
var count = 0.obs;
// 修改(自动触发 UI 刷新)
count.value++;
// UI 监听
Obx(() => Text('${controller.count}'))
2.2 .obs 的底层原理
.obs 是 GetX 最核心的魔法。要理解它,需要拆解三个问题:
问题一:.obs 做了什么?
当你写 var count = 0.obs,实际上是把一个普通的 int 包装成了一个 RxInt 对象。这个 Rx 对象内部持有:
- 真正的值(
_value) - 一个监听者列表(
Stream)
它本质上就是一个 带通知能力的值容器。
问题二:修改值时发生了什么?
当你写 count.value++,Rx 对象的 set value 被触发。在 setter 内部:
- 更新
_value - 通过
Stream广播一个"值变了"的事件 - 所有订阅了这个 Stream 的监听者收到通知
问题三:Obx 怎么知道要监听哪些变量?
这是 GetX 最巧妙的设计。Obx 并不需要你手动告诉它"我依赖了哪些变量",它是 自动收集依赖 的。
原理分三步:
Obx在首次 build 时,先打开一个"全局监听开关"- 执行你传入的 builder 函数(比如
() => Text('${count.value}')) - 当
count.value的 getter 被调用时,Rx对象检测到"监听开关"是打开的,就把自己注册到Obx的依赖列表中 - builder 执行完毕,关闭"监听开关"
之后任何被收集到的 Rx 变量发生变化,Obx 就会自动重建。
Obx 首次 build
→ 开启"依赖收集模式"
→ 执行 builder: () => Text('${count.value}')
→ count.value 的 getter 被调用
→ count(Rx 对象)发现正在收集依赖,把自己注册进去
→ 关闭"依赖收集模式"
→ 依赖收集完成:[count]
之后 count.value++ 触发
→ Rx 广播变化
→ Obx 收到通知,重新执行 builder
这个机制和 Vue 3 的
watchEffect、MobX 的autorun原理几乎一模一样 —— 基于 getter 劫持的自动依赖收集。
2.3 GetBuilder 的底层原理
相比之下,GetBuilder 的原理简单得多:
GetBuilder在initState时,把自己注册到 Controller 的监听者列表- Controller 调用
update(),遍历列表,调用每个GetBuilder的setState GetBuilder在dispose时,从列表中移除自己
没有 Stream、没有依赖收集,就是最朴素的 观察者模式 + setState。
2.4 两种方式怎么选?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 表单页面、简单列表 | GetBuilder | 手动控制,性能开销最小 |
| 数据频繁联动、多变量交叉依赖 | Obx + .obs | 自动依赖收集,代码更简洁 |
| 超大列表、高性能场景 | GetBuilder + update([id]) | 可以精确控制刷新范围 |
三、依赖注入
3.1 是什么?
GetX 内置了一套依赖注入系统,核心 API 就两个:
Get.put(Controller())—— 注册Get.find<Controller>()—— 获取
你可以把它理解成一个 全局的"服务柜台":先把东西放进去,需要的时候按类型取出来。
3.2 底层原理
GetX 内部维护了一个 全局的 Map<String, Object>,key 是类型名(或类型名 + tag),value 是实例。
全局容器(简化理解):
{
"HomeController": HomeController 实例,
"UserService": UserService 实例,
"ApiClient_v2": ApiClient 实例(带 tag)
}
Get.put() 就是往 Map 里写,Get.find() 就是从 Map 里读。
3.3 四种注册方式的区别
| 方式 | 何时创建 | 何时销毁 | 适用场景 |
|---|---|---|---|
Get.put() | 立即创建 | 手动或路由关闭时 | 页面 Controller |
Get.lazyPut() | 首次 find 时 | 同上 | 可能用不到的依赖 |
Get.putAsync() | 立即创建(支持异步) | 同上 | 需要异步初始化的服务 |
Get.create() | 每次 find 都新建 | 不自动销毁 | 每次需要新实例的场景 |
3.4 SmartManagement:自动内存管理
GetX 最被低估的能力之一。它有一套 智能内存管理机制,可以在路由关闭时自动销毁关联的 Controller。
三种模式:
- full(默认):不被任何路由或 Widget 使用的 Controller 自动销毁
- onlyBuilder:只有通过
GetBuilder/GetX使用的 Controller 才自动管理 - keepFactory:销毁实例但保留工厂函数,下次
find时重新创建
这解决了 Flutter 状态管理中一个常见的痛点:谁来负责销毁 Controller? 在 Provider/Bloc 中你需要手动处理,GetX 帮你自动化了。
四、路由管理
4.1 为什么要替换 Flutter 原生路由?
Flutter 原生路由的痛点:
- 跳转需要
context,在非 Widget 层(Service、Controller)中很难拿到 - 传参和接收返回值写法繁琐
- 路由动画自定义复杂
GetX 的路由通过全局 NavigatorKey 持有 Navigator 的引用,所以 不需要 context 就能跳转。
4.2 底层原理
GetX 路由的核心做了两件事:
第一:全局 NavigatorKey
GetX 在 GetMaterialApp 初始化时,创建了一个全局的 GlobalKey<NavigatorState>,保存在静态变量中。之后所有路由操作都通过这个 key 拿到 Navigator,不再依赖 context。
第二:路由与依赖注入联动
这是 GetX 路由最独特的地方。当你用 Get.to(HomePage()) 跳转时:
- 创建一个路由条目
- 如果 HomePage 关联了 Controller(通过
GetBuilder或Bindings),自动put进依赖容器 - 当路由
pop时,自动delete关联的 Controller
Get.to(HomePage())
→ 创建路由
→ 自动注册 HomeController(如果有 Binding)
→ 用户在 HomePage 操作...
→ Get.back()
→ 路由 pop
→ 自动销毁 HomeController
→ 内存释放
这就形成了一个 路由驱动的生命周期管理:Controller 的生死和页面的进出自动绑定。
4.3 Bindings:依赖与路由的桥梁
Bindings 是连接路由和依赖注入的纽带。它定义了"进入某个页面时需要准备哪些依赖"。
你可以把它类比为 iOS 的 viewDidLoad —— 页面加载时做初始化工作,页面销毁时自动清理。
4.4 中间件(Middleware)
GetX 路由支持中间件,可以在路由跳转前/后插入逻辑:
- 登录拦截:未登录自动跳转登录页
- 权限检查:没有权限的页面拒绝访问
- 埋点:自动记录页面访问
中间件按优先级执行,可以中断跳转(返回 null 表示拦截),和 Web 框架的中间件概念一致。
五、GetX 的其他能力
GetX 是个全家桶,除了三大核心模块,还打包了很多实用工具:
| 能力 | 说明 |
|---|---|
| 国际化(i18n) | 'hello'.tr 即可翻译,动态切换语言 |
| 主题切换 | Get.changeTheme() 一行切换深色/浅色 |
| 网络请求 | GetConnect 封装了 HTTP 客户端 |
| 本地存储 | GetStorage 类似 SharedPreferences 但更快 |
| 响应式表单验证 | 配合 .obs 做实时校验 |
| Snackbar / Dialog / BottomSheet | 不需要 context 的全局弹窗 |
| Worker | ever / debounce / interval 等响应式工具 |
Worker 机制
Worker 是 GetX 响应式系统中很实用的工具,用于对 .obs 变量的变化做 节流、防抖、一次性监听 等处理:
| Worker | 行为 |
|---|---|
ever(count, callback) | 每次变化都执行 |
once(count, callback) | 只在第一次变化时执行 |
debounce(count, callback) | 停止变化后一段时间才执行(搜索场景) |
interval(count, callback) | 变化期间按固定间隔执行(节流) |
底层实现就是对 Rx 的 Stream 做了 listen / first / debounceTime / throttle 等 Dart Stream 操作的封装。
六、GetX 的底层架构总结
把所有模块串起来,GetX 的底层可以概括为三个核心机制:
1. Rx + Stream:响应式引擎
.obs 变量(Rx 对象)
└── 内部持有 Stream
└── Obx / Worker 订阅 Stream
└── 变量变化 → Stream 广播 → 订阅者响应
这是 Dart 语言自带的 Stream 机制,GetX 没有发明新东西,只是在 Stream 之上做了 语法糖封装(.obs、Obx、ever 等),降低了使用门槛。
2. 全局 Map:依赖注入容器
静态 Map<String, InstanceInfo>
└── Get.put() 写入
└── Get.find() 读取
└── Get.delete() 删除
└── SmartManagement 自动清理
没有复杂的 IoC 容器,就是一个 Map。简单直接。
3. 全局 NavigatorKey:脱离 context 的路由
GetMaterialApp 初始化 → 持有全局 NavigatorKey
└── Get.to() / Get.back() → 通过 Key 拿到 Navigator → 执行路由操作
└── 路由变化 → 触发 Bindings → 联动依赖注入的创建/销毁
七、GetX 的争议
赞成派观点
- 开发效率极高:原型开发、中小项目飞速
- 学习曲线平缓:API 直觉化,新手友好
- 全家桶一站式:不用在多个库之间做选型和协调
反对派观点
- 过度封装:把 Flutter 的很多设计理念(如 BuildContext、InheritedWidget)绕过了,新手可能对 Flutter 本身理解不深
- 隐式行为多:自动依赖收集、自动销毁,出了问题难以调试
- 大型项目维护难:全局状态 + 隐式依赖,随着项目变大,依赖关系会变得不透明
- 和 Flutter 官方方向渐行渐远:Flutter 团队推崇的是 Riverpod / Provider 思路
客观建议
| 项目类型 | 推荐度 | 建议 |
|---|---|---|
| 个人项目 / Demo | 强烈推荐 | 快速出活 |
| 中小型商业项目 | 推荐 | 配合良好的分层架构使用 |
| 大型团队协作项目 | 谨慎 | 建议考虑 Riverpod / Bloc,或严格约束 GetX 的使用范围 |
| 学习 Flutter 阶段 | 不推荐先学 | 先理解 Flutter 原生机制,再用 GetX 提效 |
八、GetX vs 其他状态管理方案
| 维度 | GetX | Provider | Riverpod | Bloc |
|---|---|---|---|---|
| 学习成本 | 低 | 低 | 中 | 高 |
| 模板代码量 | 极少 | 少 | 中 | 多 |
| 依赖 context | 不需要 | 需要 | 不需要 | 需要 |
| 内置路由 | 有 | 无 | 无 | 无 |
| 内置依赖注入 | 有 | 自身就是 DI | 自身就是 DI | 无(需配合) |
| 可测试性 | 中 | 高 | 高 | 高 |
| 官方推荐 | 否 | 是(早期) | 是(现在) | 社区主流 |
| 适合规模 | 小中型 | 中型 | 中大型 | 大型 |
九、一句话总结
GetX 的哲学是 "约定优于配置,简单优于正确"。它牺牲了一些架构上的严谨性,换来了极致的开发效率。理解它的底层原理(Rx Stream + 全局 Map + 全局 NavigatorKey),你就能用好它,也知道它的边界在哪里。