第7章 - 状态管理入门
嗨,朋友!我是长安。
前面我们学会了用 Widget 搭建界面,但界面只是静态的。要让应用动起来,就需要管理状态(State)。这一章,我们来学习 Flutter 中最基础的状态管理方法。
🤔 什么是状态?
状态(State)就是应用中会变化的数据。
举几个例子:
- 计数器的数字:0 → 1 → 2 → 3...
- 登录状态:未登录 → 已登录
- 购物车商品数量:0 → 1 → 2...
- 表单输入内容:'' → 'H' → 'He' → 'Hello'
当状态变化时,UI 需要跟着更新,这就是状态管理要解决的问题。
📦 setState - 最基础的状态管理
setState 是 Flutter 中最简单的状态管理方式,适合组件内部的简单状态。
基本用法
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
// 定义状态
int _count = 0;
// 更新状态的方法
void _increment() {
setState(() {
_count++; // 在 setState 中修改状态
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('计数器')),
body: Center(
child: Text(
'$_count',
style: const TextStyle(fontSize: 60),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
}
setState 的工作原理
用户点击按钮
↓
调用 _increment()
↓
setState(() { _count++ })
↓
Flutter 标记 Widget 需要重建
↓
重新执行 build() 方法
↓
UI 更新显示新的 _count 值
注意
一定要在 setState 回调函数内部修改状态!直接修改不会触发 UI 更新。
// ❌ 错误:直接修改,UI 不会更新
void _increment() {
_count++;
}
// ✅ 正确:在 setState 中修改
void _increment() {
setState(() {
_count++;
});
}
🎯 实战:待办事项列表
让我们做一个稍微复杂点的例子:一个可以添加和删除的待办事项列表。
class TodoPage extends StatefulWidget {
const TodoPage({super.key});
State<TodoPage> createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage> {
// 状态:待办事项列表
final List<String> _todos = [];
// 输入框控制器
final TextEditingController _controller = TextEditingController();
// 添加待办
void _addTodo() {
if (_controller.text.trim().isEmpty) return;
setState(() {
_todos.add(_controller.text.trim());
});
_controller.clear(); // 清空输入框
}
// 删除待办
void _removeTodo(int index) {
setState(() {
_todos.removeAt(index);
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('待办事项')),
body: Column(
children: [
// 输入区域
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: '输入待办事项...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: _addTodo,
child: const Text('添加'),
),
],
),
),
// 列表区域
Expanded(
child: _todos.isEmpty
? const Center(child: Text('暂无待办事项'))
: ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(_todos[index]),
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => _removeTodo(index),
),
);
},
),
),
],
),
);
}
}
🔄 父子组件间的状态传递
当状态需要在多个组件之间共享时,通常把状态提升到父组件。
状态提升模式
// 父组件:持有状态
class ParentWidget extends StatefulWidget {
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
Widget build(BuildContext context) {
return Column(
children: [
// 子组件1:显示数据
DisplayWidget(count: _count),
// 子组件2:操作数据
ButtonWidget(onPressed: _increment),
],
);
}
}
// 子组件1:只负责显示(无状态)
class DisplayWidget extends StatelessWidget {
final int count;
const DisplayWidget({super.key, required this.count});
Widget build(BuildContext context) {
return Text('Count: $count', style: const TextStyle(fontSize: 24));
}
}
// 子组件2:只负责触发事件(无状态)
class ButtonWidget extends StatelessWidget {
final VoidCallback onPressed;
const ButtonWidget({super.key, required this.onPressed});
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: const Text('增加'),
);
}
}
数据流向
父组件(持有状态)
↙ ↘
状态数据 ↓ ↓ 回调函数
↓ ↓
DisplayWidget ButtonWidget
(显示数据) (触发事件)
↓
调用 onPressed
↓
父组件 setState
↓
UI 更新
📝 表单状态管理
表单是常见的状态管理场景,Flutter 提供了 Form 和 TextFormField 来简化表单处理。
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
String _username = '';
String _password = '';
void _submit() {
// 验证表单
if (_formKey.currentState!.validate()) {
// 保存表单数据
_formKey.currentState!.save();
// 处理登录逻辑
print('用户名: $_username, 密码: $_password');
}
}
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(labelText: '用户名'),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入用户名';
}
return null;
},
onSaved: (value) => _username = value!,
),
const SizedBox(height: 16),
TextFormField(
decoration: const InputDecoration(labelText: '密码'),
obscureText: true,
validator: (value) {
if (value == null || value.length < 6) {
return '密码至少6位';
}
return null;
},
onSaved: (value) => _password = value!,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _submit,
child: const Text('登录'),
),
],
),
);
}
}
🎛️ 复选框和开关状态
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
bool _darkMode = false;
bool _notifications = true;
bool _autoUpdate = false;
Widget build(BuildContext context) {
return ListView(
children: [
SwitchListTile(
title: const Text('深色模式'),
subtitle: const Text('使用深色主题'),
value: _darkMode,
onChanged: (value) {
setState(() {
_darkMode = value;
});
},
),
SwitchListTile(
title: const Text('通知'),
subtitle: const Text('接收推送通知'),
value: _notifications,
onChanged: (value) {
setState(() {
_notifications = value;
});
},
),
CheckboxListTile(
title: const Text('自动更新'),
value: _autoUpdate,
onChanged: (value) {
setState(() {
_autoUpdate = value ?? false;
});
},
),
],
);
}
}
🌟 状态管理最佳实践
1. 状态放哪里?
| 场景 | 建议 |
|---|---|
| 仅一个组件使用 | 放在该组件内(setState) |
| 父子组件共享 | 提升到最近的公共父组件 |
| 多个页面共享 | 使用状态管理库(下一步) |
| 全局状态 | 使用 Provider/Riverpod 等 |
2. setState 的注意事项
// ✅ 好的做法:setState 内只修改状态
void _updateName(String name) {
setState(() {
_name = name;
});
}
// ❌ 不好的做法:在 setState 内做复杂计算
void _updateData() {
setState(() {
// 不要在这里做网络请求或复杂计算
_data = someHeavyCalculation(); // 不推荐
});
}
// ✅ 更好的做法:先计算,再 setState
void _updateData() async {
final result = await someHeavyCalculation();
setState(() {
_data = result;
});
}
3. 避免不必要的 rebuild
// ❌ 每次 build 都创建新的回调函数
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => _doSomething(), // 每次都是新函数
child: Text('点击'),
);
}
// ✅ 使用方法引用或提前定义
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _doSomething, // 方法引用
child: Text('点击'),
);
}
📝 小结
这一章我们学习了:
- ✅ 什么是状态(State)
- ✅ setState 的使用方法和原理
- ✅ 实战:待办事项列表
- ✅ 父子组件间的状态传递(状态提升)
- ✅ 表单状态管理
- ✅ 状态管理最佳实践
进阶提示
setState 适合简单场景。当应用变复杂时,建议学习:
- Provider - 官方推荐的简单状态管理
- Riverpod - Provider 的进化版
- Bloc - 适合大型项目
- GetX - 简单易用
💪 练习题
- 创建一个带计数功能的购物车组件(显示商品数量,可增减)
- 实现一个主题切换功能(在亮色和暗色主题之间切换)
- 创建一个简单的评分组件(5颗星,点击变色)
点击查看答案
练习1:购物车组件
class CartItem extends StatefulWidget {
const CartItem({super.key});
State<CartItem> createState() => _CartItemState();
}
class _CartItemState extends State<CartItem> {
int _quantity = 1;
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: _quantity > 1
? () => setState(() => _quantity--)
: null,
icon: const Icon(Icons.remove),
),
Text('$_quantity', style: const TextStyle(fontSize: 20)),
IconButton(
onPressed: () => setState(() => _quantity++),
icon: const Icon(Icons.add),
),
],
);
}
}
练习3:评分组件
class RatingStars extends StatefulWidget {
const RatingStars({super.key});
State<RatingStars> createState() => _RatingStarsState();
}
class _RatingStarsState extends State<RatingStars> {
int _rating = 0;
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(5, (index) {
return IconButton(
onPressed: () => setState(() => _rating = index + 1),
icon: Icon(
index < _rating ? Icons.star : Icons.star_border,
color: Colors.amber,
size: 32,
),
);
}),
);
}
}
🚀 下一步
状态管理掌握后,下一章我们来学习 页面导航,让应用有多个页面!
由 编程指南 提供
