Appearance
第7章:Flutter 状态管理
7.1 状态管理的概念
什么是状态?
在 Flutter 中,状态是指应用中需要存储和管理的数据,这些数据会影响 UI 的显示。
常见的状态类型:
- 局部状态:只影响单个组件的状态,如按钮的点击状态、输入框的内容
- 全局状态:影响多个组件的状态,如用户登录状态、应用主题设置
- 临时状态:只在当前会话中存在的状态,如页面滚动位置
- 持久化状态:需要保存到本地存储的状态,如用户偏好设置、购物车内容
为什么需要状态管理?
当应用变得复杂时,状态管理变得至关重要:
- 组件间通信:不同组件之间需要共享和同步状态
- 状态一致性:确保应用中的状态保持一致
- 代码可维护性:集中管理状态,使代码更易于理解和维护
- 性能优化:避免不必要的重建和渲染
7.2 基础状态管理(StatefulWidget 自身状态管理)
setState() 方法
在 StatefulWidget 中,使用 setState() 方法来更新状态并触发 UI 刷新:
dart
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0; // 状态变量
void _increment() {
setState(() {
_count++; // 更新状态
});
}
void _decrement() {
setState(() {
if (_count > 0) {
_count--;
}
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
Row(
children: [
ElevatedButton(onPressed: _decrement, child: const Text('-')),
const SizedBox(width: 16),
ElevatedButton(onPressed: _increment, child: const Text('+')),
],
),
],
);
}
}状态传递(父组件向子组件)
通过构造函数将状态从父组件传递给子组件:
dart
// 子组件
class DisplayWidget extends StatelessWidget {
final int count;
const DisplayWidget({super.key, required this.count});
@override
Widget build(BuildContext context) {
return Text('Current count: $count');
}
}
// 父组件
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
DisplayWidget(count: _count), // 传递状态
ElevatedButton(onPressed: _increment, child: const Text('Increment')),
],
);
}
}7.3 父子组件状态通信
子组件向父组件传递状态(回调函数)
通过回调函数将子组件的状态传递给父组件:
dart
// 子组件
class InputWidget extends StatelessWidget {
final Function(String) onTextChanged;
const InputWidget({super.key, required this.onTextChanged});
@override
Widget build(BuildContext context) {
return TextField(
onChanged: onTextChanged, // 回调函数
decoration: const InputDecoration(
labelText: 'Enter text',
),
);
}
}
// 父组件
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
String _text = '';
void _handleTextChanged(String text) {
setState(() {
_text = text;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
InputWidget(onTextChanged: _handleTextChanged),
Text('You entered: $_text'),
],
);
}
}父子组件双向通信
结合 setState 和回调函数实现双向通信:
dart
// 子组件
class CounterButton extends StatelessWidget {
final int count;
final Function(int) onCountChanged;
const CounterButton({
super.key,
required this.count,
required this.onCountChanged,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
ElevatedButton(
onPressed: () => onCountChanged(count - 1),
child: const Text('-'),
),
const SizedBox(width: 16),
Text('$count'),
const SizedBox(width: 16),
ElevatedButton(
onPressed: () => onCountChanged(count + 1),
child: const Text('+'),
),
],
);
}
}
// 父组件
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _count = 0;
void _updateCount(int newCount) {
setState(() {
_count = newCount;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Parent count: $_count'),
CounterButton(
count: _count,
onCountChanged: _updateCount,
),
],
);
}
}7.4 跨组件状态管理(Provider)
Provider 简介
Provider 是 Flutter 官方推荐的状态管理方案,简单易用,适合中小型应用。
安装:
yaml
dependencies:
provider: ^6.1.1基本使用步骤
- 定义状态类:继承
ChangeNotifier - 提供状态:使用
ChangeNotifierProvider - 消费状态:使用
Consumer或Provider.of
示例:计数器应用
步骤 1:定义状态类
dart
import 'package:flutter/material.dart';
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 通知监听器
}
void decrement() {
if (_count > 0) {
_count--;
notifyListeners();
}
}
void reset() {
_count = 0;
notifyListeners();
}
}步骤 2:提供状态
在应用根组件中提供状态:
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}步骤 3:消费状态
使用 Consumer 消费状态:
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Consumer<CounterModel>(
builder: (context, counter, child) {
return Text(
'${counter.count}',
style: Theme.of(context).textTheme.headlineMedium,
);
},
),
],
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
Provider.of<CounterModel>(context, listen: false).increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
const SizedBox(height: 10),
FloatingActionButton(
onPressed: () {
Provider.of<CounterModel>(context, listen: false).decrement();
},
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
const SizedBox(height: 10),
FloatingActionButton(
onPressed: () {
Provider.of<CounterModel>(context, listen: false).reset();
},
tooltip: 'Reset',
child: const Icon(Icons.refresh),
),
],
),
);
}
}多个状态管理
使用 MultiProvider 管理多个状态:
dart
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CounterModel()),
ChangeNotifierProvider(create: (context) => ThemeModel()),
ChangeNotifierProvider(create: (context) => UserModel()),
],
child: const MyApp(),
),
);
}7.5 其他状态管理方案
GetX
GetX 是一个轻量级的状态管理方案,简洁高效,新手友好。
安装:
yaml
dependencies:
get: ^4.6.6使用:
dart
import 'package:get/get.dart';
class CounterController extends GetxController {
var count = 0.obs;
void increment() => count++;
void decrement() => count > 0 ? count-- : null;
void reset() => count.value = 0;
}
// 提供状态
void main() {
runApp(GetMaterialApp(
home: HomePage(),
));
}
// 消费状态
class HomePage extends StatelessWidget {
final controller = Get.put(CounterController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('GetX Counter')),
body: Center(
child: Obx(() => Text('Count: ${controller.count}')),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
child: Icon(Icons.add),
),
);
}
}Bloc
Bloc 是一个基于流的状态管理方案,适合复杂项目,规范性强。
安装:
yaml
dependencies:
flutter_bloc: ^8.1.3
bloc: ^8.1.2使用:
dart
// 事件
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
// 状态
class CounterState {
final int count;
CounterState(this.count);
}
// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
on<IncrementEvent>((event, emit) {
emit(CounterState(state.count + 1));
});
on<DecrementEvent>((event, emit) {
if (state.count > 0) {
emit(CounterState(state.count - 1));
}
});
}
}
// 使用
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterBloc(),
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: Text('Bloc Counter')),
body: Center(child: Text('Count: ${state.count}')),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
child: Icon(Icons.add),
),
);
},
),
);
}
}7.6 实操案例:主题切换应用
目标
使用 Provider 实现主题切换功能,让用户可以在亮色和暗色主题之间切换。
步骤 1:创建主题模型
dart
import 'package:flutter/material.dart';
class ThemeModel extends ChangeNotifier {
bool _isDarkMode = false;
bool get isDarkMode => _isDarkMode;
ThemeData get themeData => _isDarkMode ? darkTheme : lightTheme;
void toggleTheme() {
_isDarkMode = !_isDarkMode;
notifyListeners();
}
static final lightTheme = ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
backgroundColor: Colors.white,
textTheme: const TextTheme(
bodyLarge: TextStyle(color: Colors.black),
),
);
static final darkTheme = ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
backgroundColor: Colors.grey[900],
textTheme: const TextTheme(
bodyLarge: TextStyle(color: Colors.white),
),
);
}步骤 2:提供主题状态
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_model.dart';
import 'home_page.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => ThemeModel(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return Consumer<ThemeModel>(
builder: (context, themeModel, child) {
return MaterialApp(
title: 'Theme Switcher',
theme: themeModel.themeData,
home: const HomePage(),
);
},
);
}
}步骤 3:创建首页
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_model.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Theme Switcher'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Hello, Flutter!',
style: TextStyle(fontSize: 24),
),
const SizedBox(height: 40),
Consumer<ThemeModel>(
builder: (context, themeModel, child) {
return Text(
'Current theme: ${themeModel.isDarkMode ? 'Dark' : 'Light'}',
style: const TextStyle(fontSize: 18),
);
},
),
const SizedBox(height: 40),
ElevatedButton(
onPressed: () {
Provider.of<ThemeModel>(context, listen: false).toggleTheme();
},
child: const Text('Toggle Theme'),
),
],
),
),
);
}
}步骤 4:运行应用
- 启动模拟器或连接真机
- 运行项目
- 点击 "Toggle Theme" 按钮,观察主题变化
7.7 新手易错点
状态更新不调用 setState()
错误:直接修改状态变量,UI 不更新。
解决方案:
- 必须在
setState()回调中修改状态 - 对于 Provider,必须调用
notifyListeners()
跨组件状态传递错误
错误:使用全局变量或静态变量来共享状态。
解决方案:
- 使用 Provider、GetX 等状态管理方案
- 使用回调函数进行组件间通信
- 避免使用全局变量,难以维护
Provider 使用错误
错误:
- 忘记在根组件提供状态
- 在
build方法中创建 Provider - 错误使用
listen参数
解决方案:
- 在应用根组件使用
ChangeNotifierProvider - 不要在
build方法中创建 Provider 实例 - 读取状态时
listen: true,修改状态时listen: false
状态管理方案选择错误
错误:选择过于复杂的状态管理方案。
解决方案:
- 小型应用:使用 setState 或 Provider
- 中型应用:使用 Provider 或 GetX
- 大型应用:使用 Bloc 或 Riverpod
性能问题
错误:
- 频繁调用 setState
- 不必要的重建
- 状态管理不当导致的性能问题
解决方案:
- 合理使用 const 构造函数
- 使用
const修饰符避免不必要的重建 - 选择合适的状态管理方案
- 使用
Select或Consumer优化重建范围
7.8 小结
本章介绍了 Flutter 的状态管理,包括基础状态管理、父子组件状态通信、跨组件状态管理等内容。状态管理是 Flutter 开发中的核心难点,掌握好状态管理对于构建复杂的 Flutter 应用至关重要。
我们学习了几种常见的状态管理方案:
- setState:适合简单的局部状态管理
- Provider:官方推荐,适合中小型应用
- GetX:简洁高效,新手友好
- Bloc:基于流,适合复杂项目
通过实操案例,我们使用 Provider 实现了主题切换功能,体验了跨组件状态管理的实现。在实际开发中,你应该根据应用的复杂度选择合适的状态管理方案。
在接下来的章节中,我们将学习 Flutter 的路由与导航,掌握如何实现页面之间的跳转和参数传递。
