什么是状态管理
什么是“状态”
在 Flutter 里,状态就是 UI 显示依赖的数据。比如:
- 一个计数器的数字(
count
) - 用户是否登录(
isLogin
) - 输入框里的文字(
text
) - 接口请求的结果(
userProfile
)
一句话:状态就是 UI 背后的数据。
什么是“状态管理”
状态管理包含两件事:
- 存储和修改状态(数据)
- 比如
count = 0;
- 点击按钮时
count++
- 比如
- 通知 UI 重新渲染
- 比如文本
"Count: $count"
需要更新 - 状态变化后 UI 自动刷新
- 比如文本
状态管理 = 修改数据 + 更新 UI。
Flutter 为什么需要状态管理?
因为 Flutter 的 UI 是声明式的: UI = f(状态)
“声明式”是什么意思?
在 命令式 UI(比如 Android 原生、Java Swing)里,你要手动告诉界面怎么改:
textView.setText("Count: " + count);
就像你在下“命令”,一步步写清楚:怎么改文字,怎么改颜色。
在 声明式 UI(Flutter / React)里,你只“声明”UI长什么样,它会根据状态自动渲染:
Text("Count: $count")
你不用写“怎么更新”,只要告诉 Flutter:UI 的样子取决于状态。
UI = f(状态) 是什么意思:
这是一个数学表达式的类比:
- UI(用户界面)是一个函数的结果。
- 状态是输入参数。
- f(函数)就是你写的
build()
方法。换句话说:
UI = build(状态)
。当状态改变时,Flutter 会重新执行build()
,生成新的 UI。
举个例子:
int count = 0;
@override
Widget build(BuildContext context) {
return Text("Count: $count");
}
- 状态:
count
- f(状态):
Text("Count: $count")
- UI:界面上显示的数字
当 count = 5
→ Text("Count: 5")
当 count = 10
→ Text("Count: 10")
因此只要 count
改了,就得 setState()
,Flutter 会重新执行 build
,然后 UI 更新。
如果没有“状态管理”,你得到处写 setState
,而且数据共享很麻烦。
状态管理的方式
自带的
setState
(最简单,局部)InheritedWidget / Provider(跨组件共享)
Bloc / Redux(比较重量级)
GetX / Riverpod / MobX(轻量又好用)
GetX 的四种状态管理方式
GetX 包
├── Obx (最轻量,只监听 Rx 变量)
├── GetX<T> (监听整个控制器的 Rx 变量)
├── GetBuilder (非响应式,手动 update)
└── ValueBuilder(局部状态,类似 StatefulWidget)
方式 | 响应方式 | 是否需要 .obs |
是否依赖控制器 | 更新触发方式 | 适用场景 |
---|---|---|---|---|---|
Obx | 自动更新 | ✅ 需要 | ❌ 不一定 | 变量变化自动刷新 | 小范围UI,频繁更新 |
GetX | 自动更新 | ✅ 需要 | ✅ 强依赖 | 控制器内变量变化 | 监听控制器多个变量 |
GetBuilder | 手动更新 | ❌ 不需要 | ✅ 强依赖 | controller.update() |
大范围UI,性能优先 |
ValueBuilder | 手动更新 | ❌ 不需要 | ❌ 不依赖 | update(value) |
局部小状态,替代StatefulWidget |
Obx
使用
- 定义响应式变量:
xxx.obs
- 使用
Obx
监听响应式变量 - 变量变化之后,会自动更新UI
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// GetMaterialApp: getx 里面的组件
return GetMaterialApp(
// 不显示 debug banner 这个图标
debugShowCheckedModeBanner: false,
home: StateObxView(),
);
}
}
class StateObxView extends StatelessWidget {
StateObxView({super.key});
// 1.定义一个响应式变量:使用 Obx() 来监听状态变化
final count = 0.obs;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Obx(...)")),
body: Center(
child: Column(
children: [
// 2.这个 Text 被 Obx 包裹,监听 count
Obx(() => Text("count1 -> $count")),
Obx(() => Text("count2 -> $count")),
//
Divider(),
ElevatedButton(
onPressed: () {
// 3.点击按钮时,更新 count 的值,同时会自动更新 UI。
// 不需要调用 setState()
count.value++;
},
child: Text('add'),
),
],
),
),
);
}
}

响应式变量
什么是“响应式变量”?
响应式变量(Reactive Variable):就是 能自动通知监听它的 UI 刷新的变量。
- 普通变量:只会存数据,改了不会通知 UI。
- 响应式变量:一旦数据变化,会“广播消息”,让相关 UI 自动更新。
final count = 0.obs;
:
0
→ 普通整数.obs
→ 把普通变量包装成“响应式变量”- 等价于:
RxInt count = RxInt(0);
count
不再是普通的int
,而是一个 RxInt(响应式 Int 类型)。
普通类型变成Rx类型
Dart自带的
普通变量 | 响应式写法 | 内部类型 |
---|---|---|
int count = 0; |
var count = 0.obs; |
RxInt |
double price = 0.0; |
var price = 0.0.obs; |
RxDouble |
String name = "Tom"; |
var name = "Tom".obs; |
RxString |
bool isLogin = false; |
var isLogin = false.obs; |
RxBool |
List<String> list = []; |
var list = <String>[].obs; |
RxList<String> |
Set<int> nums = {}; |
var nums = <int>{}.obs; |
RxSet<int> |
Map<String, int> map = {}; |
var map = <String, int>{}.obs; |
RxMap<String, int> |
自定义对象
整个对象响应式
class User {
String name;
int age;
User(this.name, this.age);
}
var user = User("Tom", 20).obs;
user.update((val) {
val?.age++;
}); // 自动刷新 UI
字段响应式
class User {
var name = "Tom".obs;
var age = 20.obs;
}
final user = User();
user.name.value = "Jerry"; // 自动刷新 UI
特殊类型
Rx<T>
→ 通用写法,可以包装任何类型:
var myData = Rx<MyClass>(MyClass());
Rxn<T>
→ 可空响应式类型(防止空指针):
Rxn<String> city = Rxn<String>();
city.value = null; // 合法
Obx
包裹的作用
Obx(() => Text("count1 -> $count")),
- 监听响应式变量
Obx
会监听你在()=>
里面用到的所有.obs
变量。- 一旦这些变量发生变化,
Obx
会触发builder
,重新构建里面的 UI。
- 局部刷新 UI
- 只会刷新
Obx
包裹的那一小块 UI,而不是整个页面。因此提高了性能。
- 只会刷新
- 自动更新
- 你不用写
setState()
,变量变化 UI 就会自动刷新。
- 你不用写
GetX
使用
使用步骤
定义控制器:
class CountController extends GetxController
- 注意要继承
GetxController
- 定义响应式变量:
final _count = 0.obs;
- 定义操作状态的方法:
add() => _count.value++;
- 注意要继承
在页面中使用
GetX
Widget- 创建控制器实例:
final controller = CountController();
(这个是在手动创建) - 绑定实例:
- 泛型
<CountController>
指定了类型:GetX<CountController>(
init
或依赖注入提供了具体实例:init: controller,
- 创建回调函数:
builder: (ctrl) {......}
。每次响应式变量发生变化的时候,都会调用builder
。
- 泛型
- 创建控制器实例:
修改状态触发 UI 更新:
.obs
变量变化时,所有用到这个变量的GetX
Widget 都会重新 build。ElevatedButton( // 点击按钮 -> count++ onPressed: () { ctrl.add(); }, child: Text('count1'), ),
代码
class CountController extends GetxController {
// RxInt 类型,响应式变量
final _count = 0.obs;
set count(value) => _count.value = value;
get count => _count.value;
// 另一个响应式变量
final _count2 = 0.obs;
set count2(value) => _count2.value = value;
get count2 => _count2.value;
add() => _count.value++;
add2() => _count2.value++;
}
class StateGetxView extends StatelessWidget {
StateGetxView({super.key});
// 实例化控制器
// 在实际项目里,更推荐用 Get.put(CountController()) 来全局注入,这样可以跨页面共享。
final controller = CountController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Getx")),
body: Center(
child: Column(
children: [
// class GetX<T extends DisposableInterface> extends StatefulWidget
GetX<CountController>(
// init: controller 表示绑定这个控制器实例。
init: controller,
// final void Function(GetXState<T> state)? initState,
// initState: (_) {} 可以在 widget 初始化时做一些操作,这里没做事。
initState: (_) {},
// final GetXControllerBuilder<T> builder;
// builder 返回一个 Text,显示 count。
// 每次 count 变化时,都会执行 print("GetX - 1"),并重建 Text。
builder: (ctrl) {
print("GetX - 1");
return Text('value 1 -> ${ctrl.count}');
},
),
GetX<CountController>(
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetX - 2");
return Text('value 2 -> ${ctrl.count}');
},
),
Divider(),
//
GetX<CountController>(
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetX - 3");
return Column(
children: [
Text('value 3 -> ${ctrl.count}'),
ElevatedButton(
// 点击按钮 -> count++
onPressed: () {
ctrl.add();
},
child: Text('count1'),
),
],
);
},
),
Divider(),
// count2
GetX<CountController>(
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetX - 4");
return Text('value 4 -> ${ctrl.count2}');
},
),
Divider(),
// 按钮
ElevatedButton(
onPressed: () {
controller.add();
},
child: Text('count1'),
),
ElevatedButton(
onPressed: () {
controller.add2();
},
child: Text('count2'),
),
],
),
),
);
}
}
执行流程图
flowchart TD A["用户点击按钮"] -->|"调用 add() 或 add2()"| B["CountController 方法执行"] B -->|"修改 _count 或 _count2"| C["RxInt 变量发生变化"] C --> D["GetX<CountController> 监听到变化"] D -->|"触发 builder 回调"| E["重新构建对应 Widget"] E --> F["UI 更新显示最新的 count / count2 值"]
GetX Widget 如何监听状态
GetX<CountController>(
builder: (ctrl) {
return Text('value -> ${ctrl.count}');
},
)
GetX
内部会 订阅 ctrl.count 的变化。
当 _count.value
改变时,它会触发通知,让所有订阅了它的 builder
被重新执行(重新 build)。
控制器里的 Rx 变量 (.obs)
│
│ 变化时发出通知
▼
GetX Widget
│
│ 收到通知
▼
builder 被执行
│
▼
UI 重建
注意:只有 .obs
类型的变量会触发通知。普通变量不会自动刷新 UI。
所以 GetxController
并不是自己去 “监控变量”,而是 依赖 Rx 对象的监听机制 来通知 GetX
重建 UI。
GetxController 的作用
存放状态
页面或者模块的状态(数据)都可以放在控制器里。
可以结合响应式变量 .obs
,实现 自动更新 UI。
注意:GetxController
并不是自己去 “监控变量”,而是 依赖 Rx 对象的监听机制 来通知 GetX
重建 UI。
class CountController extends GetxController {
var count = 0.obs; // 响应式状态
void increment() => count++;
}
提供操作方法
控制器不仅存状态,还可以写方法来修改状态,封装业务逻辑。
add() => _count.value++;
add2() => _count2.value++;
生命周期管理
GetxController
提供了一些生命周期钩子:
方法 | 作用 |
---|---|
onInit() |
控制器创建后执行一次,通常做初始化或 API 请求 |
onReady() |
控制器完全初始化并绑定 UI 后执行 |
onClose() |
控制器销毁前执行,用来释放资源,如取消订阅、关闭 Stream |
class CountController extends GetxController {
@override
void onInit() {
super.onInit();
print("Controller 初始化");
}
@override
void onClose() {
print("Controller 销毁");
super.onClose();
}
}
支持依赖注入
可以通过 Get.put()
或 Get.lazyPut()
注入控制器。
页面或其他控制器可以通过 Get.find<T>()
获取实例。
Get.put(CountController()); // 注入
final ctrl = Get.find<CountController>(); // 获取
如何绑定控制器Controller
手动创建了控制器,并通过 init
绑定:
ctrl
就是我们手动创建的 controller
。
不依赖全局注入,独立使用。
优点:
- 控制器只在这个页面有效,不占用全局资源。
- 页面销毁时,控制器也会自动销毁(默认行为,可通过
fenix: true
保持)。
缺点:
- 其他页面无法直接获取这个控制器。
- 页面多次进入时,如果不加
Get.isRegistered
检查,可能会重复创建实例。
class CountController extends GetxController {
var count = 0.obs;
void increment() => count++;
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = CountController(); // 手动创建控制器(也会重复创建实例,每次执行build都会)
return Scaffold(
body: Center(
child: GetX<CountController>(
init: controller, // 指定使用这个实例
builder: (ctrl) {
return Column(
children: [
Text('count: ${ctrl.count}'),
ElevatedButton(
onPressed: () => ctrl.increment(),
child: Text('增加'),
),
],
);
},
),
),
);
}
}
依赖 GetX 的依赖注入(不使用init
)
- 通过
Get.put(CountController())
全局注入控制器。 GetX
内部会自动使用Get.find<CountController>()
找到全局实例。- 我们在创建
GetX
的时候已经指明了泛型:child: GetX<CountController>(
- 我们在创建
ctrl
就是全局注入的控制器。
优点:
- 控制器是全局单例,整个应用都能共享。
- 生命周期从应用启动开始,到应用结束才销毁。
缺点:
- 如果只是某个页面需要,不需要全局一直存在。
class CountController extends GetxController {
var count = 0.obs;
void increment() => count++;
}
void main() {
Get.put(CountController()); // 全局注入
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: MyPage());
}
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetX<CountController>(
builder: (ctrl) {
return Column(
children: [
Text('count: ${ctrl.count}'),
ElevatedButton(
onPressed: () => ctrl.increment(),
child: Text('增加'),
),
],
);
},
),
),
);
}
}
重复创建实例
问题引入
class StateGetxView extends StatelessWidget {
StateGetxView({super.key});
final controller = CountController();// 手动创建实例(会重复创建)
// final controller2 = Get.put(CountController());// 使用依赖创建(会重复创建)
这个字段是在 每次页面实例化的时候执行一次。
如果你多次进入这个页面(比如 Navigator.push
多次),每次都会执行 CountController()
,也就是 会创建多个控制器实例。
对应的 GetX<CountController>(init: controller, ...)
每次都会绑定这个新实例。
如何避免重复创建
用依赖注入和检查是否已注册
final controller = Get.isRegistered<CountController>()
? Get.find<CountController>()
: Get.put(CountController());
- 如果控制器已经被注册过,就复用已有实例。
- 如果没有,就创建并注册一个新的实例。
全局注入一次(写在 main()
)
void main() {
Get.put(CountController()); // 全局单例
runApp(MyApp());
}
页面每次进入都会用同一个控制器,不会重复创建。
GetBuilder
使用步骤
- 使用的步骤和
GetX
类似 - 定义控制器:
class CountController extends GetxController
- 在页面中使用
GetX
Widget:GetBuilder<CountController>(
- 修改状态:会改变
count
的值,但是不会里面更新UI - 执行
controller.update
才会更新状态
class CountController extends GetxController {
// RxInt 类型,响应式变量
final _count = 0.obs;
set count(value) => _count.value = value;
get count => _count.value;
// 另一个响应式变量
final _count2 = 0.obs;
set count2(value) => _count2.value = value;
get count2 => _count2.value;
add() => _count.value++;
add2() => _count2.value++;
}
class StateGetBuilderView extends StatelessWidget {
StateGetBuilderView({super.key});
// 这个早期版本可以这么写。但是现在这么写UI是不会跟新的
// final controller = CountController();
final CountController controller = Get.put(CountController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("GetBuilder")),
body: Center(
child: Column(
children: [
GetBuilder<CountController>(
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetBuilder - 1");
return Text('value -> ${ctrl.count}');
},
),
GetBuilder<CountController>(
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetBuilder - 2");
return Text('value -> ${ctrl.count}');
},
),
Divider(),
//
GetBuilder<CountController>(
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetBuilder - 3");
return Column(
children: [
Text('value -> ${ctrl.count}'),
ElevatedButton(
onPressed: () {
ctrl.add();
},
child: Text('GetBuilder -> add'),
),
],
);
},
),
Divider(),
// count2
GetBuilder<CountController>(
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetBuilder - 4");
return Text('value count2 -> ${ctrl.count2}');
},
),
Divider(),
// id2
GetBuilder<CountController>(
id: "id2",
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetBuilder - 4");
return Text('id2 -> value count2 -> ${ctrl.count2}');
},
),
Divider(),
// 按钮
ElevatedButton(
onPressed: () {
controller.add();
},
child: Text('add'),
),
ElevatedButton(
onPressed: () {
controller.add2();
},
child: Text('add2'),
),
ElevatedButton(
onPressed: () {
controller.update();
},
child: Text('controller.update()'),
),
ElevatedButton(
onPressed: () {
controller.update(["id2"]);
},
child: Text('controller.update(id2)'),
),
],
),
),
);
}
}

使用要点
状态(变量)变化后不好自动更新UI
GetBuilder
是一个非响应式状态管理小部件,不自动监听 Rx
变量(如 _count
或_count2
)的变化。
它依赖于手动调用 controller.update()
(或 update([id])
)来触发 builder 重建。
id 问题
GetBuilder<CountController>(
id: "id2",
init: controller,
initState: (_) {},
builder: (ctrl) {
print("GetBuilder - 4");
return Text('id2 -> value count2 -> ${ctrl.count2}');
},
),
调用 controller.update()
会确通知所有 GetBuilder
(无 id
)重建。
调用 controller.update(["id2"])
只通知指定 id: "id2"
的 GetBuilder
重建。
要使用Get.put(CountController())
原来我是在代码中直接创建:
final controller = CountController(); // 直接实例化
直接创建 CountController
实例没有将其注册到 GetX
的依赖管理系统。虽然 GetBuilder
的 init: controller
使用了同一个 controller
实例,但 GetX
内部无法正确关联这个实例与 GetBuilder
的状态,导致 controller.update()
无法触发 UI 重建。
通过 final controller = Get.put(CountController());
,Get.put()
将 CountController
实例注入到 GetX
的全局依赖管理中,GetX
会跟踪这个实例。
ValueBuilder
ValueBuilder 是什么
ValueBuilder<T>
是 GetX 提供的一种轻量级状态管理组件,用于管理 单个局部状态。
它的特点:
- 内部自带状态,不需要额外控制器。
- 提供 builder 回调来渲染 UI。
- 提供
updateFn
来修改状态,修改后自动刷新 UI。 - 可选
onUpdate
和onDispose
钩子,方便调试和释放资源。
案例
class StateValueBuilderView extends StatelessWidget {
const StateValueBuilderView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("ValueBuilder")),
body: Column(
children: [
Center(
child: ValueBuilder<int?>(
initialValue: 10,
builder: (value, updateFn) {
return Column(
children: [
Text("count -> $value"),
ElevatedButton(
onPressed: () {
updateFn(value! + 1);
},
child: Text('ValueBuilder -> add'),
),
],
);
},
onUpdate: (value) => print("Value updated: $value"),
onDispose: () => print("Widget unmounted"),
),
),
],
),
);
}
}

代码讲解
ValueBuilder<int?>(
initialValue: 10,
builder: (value, updateFn) { ... },
)
ValueBuilder<int?>
泛型的作用:
这里的
<int?>
表示:- 状态类型是 int
- 可以为空(
?
表示 nullable)
泛型的作用:
builder
回调里的value
就是T
类型 (int?
)updateFn
的参数也必须是T
类型 (int?
)
initialValue: 10
:给builder的value一个初始值
builder
会在 状态变化时自动调用。
value
→ 当前状态值
updateFn
→ 用于修改状态的函数
ElevatedButton(
onPressed: () {
updateFn(value! + 1);
},
)
调用 updateFn(newValue)
:
_value
更新为newValue
- 自动触发 重新 build builder
- UI 显示最新状态
onUpdate: (value) => print("Value updated: $value")
onUpdate
:状态更新时触发
onDispose: () => print("Widget unmounted")
onDispose
:ValueBuilder 销毁时触发(页面退出、Widget 被移除)
流程图
[初始化]
│ initialValue = 10
▼
[第一次 builder 执行]
│ value = 10
▼
[UI 渲染] -> Text("count -> 10")
│
└─用户点击按钮调用 updateFn(value+1)
│
▼
[_value 更新]
│
▼
[触发 builder 再次执行]
│ value = 11
▼
[UI 重建] -> Text("count -> 11")
│
▼
[可选触发 onUpdate]