📚 第一章:状态管理概述
1.1 什么是状态(State)
在 Flutter 中,状态是指任何在应用运行时可能发生变化的数据。UI 是状态的函数:UI = f(State)。当状态改变时,Flutter 会重新构建受影响的 Widget 树来反映最新的数据。
状态无处不在:用户输入的文本、购物车中的商品列表、是否已登录、当前选中的选项卡、从网络加载的数据等等,都属于状态。
1.2 临时状态 vs 应用状态
Flutter 团队将状态划分为两大类,理解这一区分对于选择合适的管理方案至关重要:
临时状态(Ephemeral State)
- 又称 UI 状态或局部状态
- 仅影响单个 Widget
- 例:PageView 的当前页、动画进度、BottomNavigationBar 的选中项
- 通常用
setState管理即可 - 不需要序列化,不需要跨 Widget 共享
应用状态(App State)
- 又称共享状态或全局状态
- 需要在多个 Widget 之间共享
- 例:用户登录信息、购物车内容、通知设置、文章列表
- 需要使用专业的状态管理方案
- 可能需要持久化和序列化
1.3 为什么状态管理很重要
随着应用规模增长,如果没有良好的状态管理策略,代码会变得混乱不堪:
- 数据一致性:多处展示同一数据时,如何确保它们同步更新
- 代码可维护性:状态逻辑与 UI 逻辑分离,便于测试和重构
- 性能优化:精确控制哪些 Widget 需要重建,避免不必要的渲染
- 团队协作:统一的状态管理模式让团队成员更容易理解代码
- 调试便利:可追踪的状态变化使 Bug 更容易定位
1.4 状态管理方案的演进
Flutter 社区已经发展出多种成熟的状态管理方案,从简单到复杂:
- setState — Flutter 内置,最基础的方式
- InheritedWidget — Flutter 内置的状态传递机制
- ValueNotifier / ChangeNotifier — Flutter 内置的观察者模式
- Provider — 官方推荐,对 InheritedWidget 的封装
- Riverpod — Provider 作者的全新力作,编译时安全
- BLoC — 基于流(Stream)的业务逻辑组件
- GetX — 轻量级全能框架
🔄 第二章:setState
2.1 工作原理
setState 是 StatefulWidget 中最基本的状态管理方式。调用 setState() 会通知 Flutter 框架:这个 State 对象的内部状态发生了变化,需要重新调用 build() 方法来更新 UI。
其内部流程如下:
- 调用
setState(VoidCallback fn) - 执行回调函数
fn,修改状态变量 - 将当前 Element 标记为"dirty"(需要重建)
- 在下一帧,Flutter 调用
build()重新构建该 Widget 子树
dart // setState 的源码简化版 void setState(VoidCallback fn) { // 执行回调修改状态 fn(); // 将 Element 标记为 dirty,等待下一帧重建 _element!.markNeedsBuild(); }
2.2 基本使用
dart class CounterPage extends StatefulWidget { const CounterPage({super.key}); @override State<CounterPage> createState() => _CounterPageState(); } class _CounterPageState extends State<CounterPage> { int _count = 0; void _increment() { setState(() { _count++; }); } void _decrement() { setState(() { if (_count > 0) _count--; }); } void _reset() { setState(() { _count = 0; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('计数器: $_count')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '$_count', style: TextStyle(fontSize: 80, fontWeight: FontWeight.bold), ), SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton(onPressed: _decrement, child: Icon(Icons.remove)), SizedBox(width: 16), ElevatedButton(onPressed: _reset, child: Icon(Icons.refresh)), SizedBox(width: 16), ElevatedButton(onPressed: _increment, child: Icon(Icons.add)), ], ), ], ), ), ); } }
2.3 最佳实践
- 在回调内修改状态:始终在
setState的回调函数内修改变量,保持代码清晰 - 最小化 setState 影响范围:将频繁变化的部分提取为独立的 StatefulWidget
- 避免在 build 中调用 setState:这会导致无限循环
- 检查 mounted 属性:异步操作完成后调用 setState 前,先检查 Widget 是否还在树中
dart // 异步操作中安全使用 setState Future<void> _loadData() async { final data = await fetchFromApi(); // 检查 Widget 是否还挂载在树上 if (!mounted) return; setState(() { _data = data; _isLoading = false; }); }
2.4 反模式(Anti-patterns)
dart // 反模式 1: 在回调外修改状态 _count++; // 先修改了 setState(() {}); // 空回调 - 虽然能工作但不推荐 // 反模式 2: 在 build 中调用 setState @override Widget build(BuildContext context) { setState(() { _x = 1; }); // 无限循环! return Container(); } // 反模式 3: 在整个页面级别使用 setState 更新小组件 // 会导致整个页面重建,性能浪费 // 反模式 4: 异步后不检查 mounted Future<void> _fetch() async { final data = await api.get(); setState(() { _data = data; }); // Widget 可能已被销毁! }
🔗 第三章:InheritedWidget
3.1 工作原理
InheritedWidget 是 Flutter 内置的状态传递机制,它允许数据沿 Widget 树向下传递,而不需要逐层通过构造函数传参。Theme.of(context)、MediaQuery.of(context) 等底层 API 都基于这个机制。
当 InheritedWidget 的数据发生变化时,所有通过 dependOnInheritedWidgetOfExactType 订阅了它的后代 Widget 都会被通知重建。
3.2 自定义 InheritedWidget
dart // 1. 定义共享数据和 InheritedWidget class UserData { final String name; final String email; final String avatarUrl; const UserData({ required this.name, required this.email, required this.avatarUrl, }); } class UserDataWidget extends InheritedWidget { final UserData userData; const UserDataWidget({ super.key, required this.userData, required super.child, }); // 提供便捷的 of(context) 方法 static UserData of(BuildContext context) { final widget = context .dependOnInheritedWidgetOfExactType<UserDataWidget>(); assert(widget != null, 'No UserDataWidget found in context'); return widget!.userData; } // 可选:不订阅更新的读取方式 static UserData? maybeOf(BuildContext context) { return context .dependOnInheritedWidgetOfExactType<UserDataWidget>() ?.userData; } @override bool updateShouldNotify(UserDataWidget oldWidget) { // 只有在数据确实改变时才通知后代 return userData.name != oldWidget.userData.name || userData.email != oldWidget.userData.email; } }
3.3 使用 InheritedWidget
dart // 2. 在顶层提供数据 class MyApp extends StatefulWidget { @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { UserData _user = UserData( name: '张三', email: 'zhang@example.com', avatarUrl: 'https://example.com/avatar.png', ); @override Widget build(BuildContext context) { return UserDataWidget( userData: _user, child: MaterialApp(home: HomePage()), ); } } // 3. 在任意后代 Widget 中读取数据 class UserProfile extends StatelessWidget { @override Widget build(BuildContext context) { // 通过 of(context) 获取数据 final user = UserDataWidget.of(context); return Column( children: [ CircleAvatar(backgroundImage: NetworkImage(user.avatarUrl)), Text(user.name, style: TextStyle(fontSize: 20)), Text(user.email), ], ); } }
3.4 of(context) 模式详解
of(context) 是 Flutter 中非常重要的设计模式,几乎所有内置服务都采用这种方式:
Theme.of(context)— 获取主题数据MediaQuery.of(context)— 获取屏幕尺寸信息Navigator.of(context)— 获取导航器Scaffold.of(context)— 获取 Scaffold 状态
调用 dependOnInheritedWidgetOfExactType 会让当前 Widget 订阅该 InheritedWidget 的更新。如果只是读取而不需要订阅,可以使用 getInheritedWidgetOfExactType 代替。
🔔 第四章:ValueNotifier 与 ChangeNotifier
4.1 ValueNotifier
ValueNotifier 是一个持有单个值并在值改变时通知监听器的简单类。它是 ChangeNotifier 的子类,非常适合管理简单的单一状态值。
dart // ValueNotifier 持有单个值 final counter = ValueNotifier<int>(0); // 修改值 - 自动通知监听器 counter.value++; counter.value = 42; // 手动添加监听器 counter.addListener(() { print('新值: ${counter.value}'); }); // 记得在不需要时移除监听器 counter.removeListener(myListener); counter.dispose();
4.2 ValueListenableBuilder
ValueListenableBuilder 是配合 ValueNotifier 使用的 Widget,它只重建自身包裹的部分,性能优秀。
dart class CounterPage extends StatefulWidget { @override State<CounterPage> createState() => _CounterPageState(); } class _CounterPageState extends State<CounterPage> { // 定义在 State 中,随 State 的生命周期管理 final ValueNotifier<int> _counter = ValueNotifier<int>(0); @override void dispose() { _counter.dispose(); // 务必释放资源 super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('ValueNotifier 示例')), body: Center( child: ValueListenableBuilder<int>( valueListenable: _counter, // 只有这个 builder 会在值变化时重建 builder: (context, value, child) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ child!, // 不变的部分,不会重建 Text('$value', style: TextStyle(fontSize: 60)), ], ); }, child: Text('当前计数'), // 缓存的子 Widget ), ), floatingActionButton: FloatingActionButton( onPressed: () => _counter.value++, child: Icon(Icons.add), ), ); } }
4.3 ChangeNotifier
ChangeNotifier 是一个可以被监听的类,适合管理更复杂的多字段状态。它是 Provider 和很多状态管理方案的基础。
dart // 定义一个完整的状态模型 class ShoppingCart extends ChangeNotifier { final List<CartItem> _items = []; // 对外暴露不可修改的列表 List<CartItem> get items => List.unmodifiable(_items); int get itemCount => _items.length; double get totalPrice => _items.fold(0, (sum, item) => sum + item.price * item.quantity); void addItem(CartItem item) { final index = _items.indexWhere((e) => e.id == item.id); if (index >= 0) { _items[index] = _items[index].copyWith( quantity: _items[index].quantity + 1, ); } else { _items.add(item); } notifyListeners(); // 通知所有监听器 } void removeItem(String id) { _items.removeWhere((e) => e.id == id); notifyListeners(); } void clear() { _items.clear(); notifyListeners(); } }
📦 第五章:Provider
5.1 概述
Provider 是 Flutter 官方推荐的状态管理方案,由 Remi Rousselet 开发。它是对 InheritedWidget 的高级封装,提供了简洁、高效、可测试的状态管理能力。
yaml # pubspec.yaml dependencies: provider: ^6.1.1
5.2 ChangeNotifierProvider
最常用的 Provider 类型,用于提供一个 ChangeNotifier 实例,并在其调用 notifyListeners() 时自动通知消费者重建。
dart // 在 Widget 树顶层提供状态 void main() { runApp( ChangeNotifierProvider( create: (_) => TodoModel(), child: MyApp(), ), ); } // 在任意后代中读取状态 // 方式1: context.watch - 监听变化并重建 final todos = context.watch<TodoModel>(); // 方式2: context.read - 只读取一次,不监听(适合在事件处理中使用) context.read<TodoModel>().addTodo('新任务'); // 方式3: context.select - 只监听特定字段 final count = context.select<TodoModel, int>( (model) => model.completedCount, );
5.3 Consumer 和 Selector
Consumer 和 Selector 是 Widget 形式的监听方式,可以精确控制重建范围,避免不必要的 rebuild。
dart // Consumer - 监听整个 Model 的变化 Consumer<TodoModel>( builder: (context, todoModel, child) { return Text('任务数: ${todoModel.todos.length}'); }, ) // Selector - 只在特定值变化时重建,性能更优 Selector<TodoModel, int>( selector: (_, model) => model.completedCount, builder: (context, completedCount, child) { return Text('已完成: $completedCount'); }, )
5.4 MultiProvider
当需要提供多个状态时,使用 MultiProvider 避免嵌套。
dart runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AuthModel()), ChangeNotifierProvider(create: (_) => TodoModel()), ChangeNotifierProvider(create: (_) => ThemeModel()), // ProxyProvider 可以依赖其他 Provider ProxyProvider<AuthModel, ApiService>( update: (_, auth, __) => ApiService(auth.token), ), ], child: MyApp(), ), );
5.5 完整 TODO 应用示例
dart // ===== 数据模型 ===== class Todo { final String id; final String title; final bool completed; const Todo({ required this.id, required this.title, this.completed = false, }); Todo copyWith({String? title, bool? completed}) { return Todo( id: id, title: title ?? this.title, completed: completed ?? this.completed, ); } } // ===== 状态管理模型 ===== class TodoModel extends ChangeNotifier { final List<Todo> _todos = []; List<Todo> get todos => List.unmodifiable(_todos); int get totalCount => _todos.length; int get completedCount => _todos.where((t) => t.completed).length; int get pendingCount => totalCount - completedCount; void add(String title) { _todos.add(Todo( id: DateTime.now().millisecondsSinceEpoch.toString(), title: title, )); notifyListeners(); } void toggle(String id) { final index = _todos.indexWhere((t) => t.id == id); if (index != -1) { _todos[index] = _todos[index].copyWith( completed: !_todos[index].completed, ); notifyListeners(); } } void remove(String id) { _todos.removeWhere((t) => t.id == id); notifyListeners(); } } // ===== 主应用 ===== class TodoApp extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('待办事项'), actions: [ // 只在 completedCount 变化时重建 Selector<TodoModel, int>( selector: (_, m) => m.completedCount, builder: (_, count, __) => Center( child: Padding( padding: EdgeInsets.all(16), child: Text('完成: $count'), ), ), ), ], ), body: Consumer<TodoModel>( builder: (context, model, _) { if (model.todos.isEmpty) { return Center(child: Text('暂无待办事项')); } return ListView.builder( itemCount: model.todos.length, itemBuilder: (_, index) { final todo = model.todos[index]; return ListTile( leading: Checkbox( value: todo.completed, onChanged: (_) => model.toggle(todo.id), ), title: Text( todo.title, style: TextStyle( decoration: todo.completed ? TextDecoration.lineThrough : null, ), ), trailing: IconButton( icon: Icon(Icons.delete_outline), onPressed: () => model.remove(todo.id), ), ); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddDialog(context), child: Icon(Icons.add), ), ); } void _showAddDialog(BuildContext context) { final controller = TextEditingController(); showDialog( context: context, builder: (_) => AlertDialog( title: Text('添加待办'), content: TextField( controller: controller, autofocus: true, decoration: InputDecoration(hintText: '输入任务内容'), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('取消'), ), ElevatedButton( onPressed: () { if (controller.text.isNotEmpty) { context.read<TodoModel>().add(controller.text); Navigator.pop(context); } }, child: Text('添加'), ), ], ), ); } }
context.read 而非 context.watch,因为回调中不需要监听变化。context.watch 只应在 build 方法中使用。
🚀 第六章:Riverpod
6.1 概述
Riverpod 是 Provider 作者 Remi Rousselet 开发的新一代状态管理方案。它解决了 Provider 的诸多限制:不依赖 BuildContext、编译时安全、支持多个同类型 Provider、更好的异步支持。
yaml # pubspec.yaml dependencies: flutter_riverpod: ^2.4.9 riverpod_annotation: ^2.3.3 dev_dependencies: riverpod_generator: ^2.3.9 build_runner: ^2.4.8
6.2 Provider 类型
Provider
暴露一个只读值,不可修改。适合计算/派生值。
StateProvider
暴露一个可修改的简单值(int、String 等)。
StateNotifierProvider
暴露一个 StateNotifier,适合复杂的状态逻辑。
FutureProvider
暴露一个 Future 的结果,自动处理加载和错误状态。
StreamProvider
暴露一个 Stream 的结果,适合实时数据。
NotifierProvider
Riverpod 2.0 推荐,替代 StateNotifierProvider。
6.3 基础用法
dart import 'package:flutter_riverpod/flutter_riverpod.dart'; // 1. 用 ProviderScope 包裹应用 void main() { runApp( ProviderScope(child: MyApp()), ); } // 2. 定义全局 Provider(顶层变量,编译时安全) final counterProvider = StateProvider<int>((ref) => 0); // 3. 在 Widget 中消费(继承 ConsumerWidget) class CounterPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // ref.watch: 监听变化并重建 final count = ref.watch(counterProvider); return Scaffold( body: Center(child: Text('$count', style: TextStyle(fontSize: 60))), floatingActionButton: FloatingActionButton( onPressed: () { // ref.read: 只读取,不监听(用于事件回调) ref.read(counterProvider.notifier).state++; }, child: Icon(Icons.add), ), ); } }
6.4 ref.watch / ref.read / ref.listen
dart class MyPage extends ConsumerStatefulWidget { @override ConsumerState<MyPage> createState() => _MyPageState(); } class _MyPageState extends ConsumerState<MyPage> { @override void initState() { super.initState(); // ref.listen: 监听变化执行副作用(显示 SnackBar 等) ref.listenManual(counterProvider, (prev, next) { if (next == 10) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('已到达 10!')), ); } }); } @override Widget build(BuildContext context) { // ref.watch: 在 build 中使用,自动重建 final count = ref.watch(counterProvider); return Text('$count'); } }
6.5 AsyncValue 处理异步状态
dart // FutureProvider 自动处理异步 final userProvider = FutureProvider<User>((ref) async { final response = await http.get(Uri.parse('https://api.example.com/user')); return User.fromJson(jsonDecode(response.body)); }); // 在 Widget 中使用 AsyncValue class UserPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final userAsync = ref.watch(userProvider); // AsyncValue 的模式匹配 - 优雅处理三种状态 return userAsync.when( loading: () => CircularProgressIndicator(), error: (error, stack) => Text('错误: $error'), data: (user) => Text('你好, ${user.name}'), ); } }
6.6 Family 和 autoDispose
dart // Family: 带参数的 Provider final todoProvider = FutureProvider.family<Todo, int>((ref, todoId) async { final response = await http.get( Uri.parse('https://api.example.com/todos/$todoId'), ); return Todo.fromJson(jsonDecode(response.body)); }); // 使用时传入参数 final todo = ref.watch(todoProvider(42)); // autoDispose: 不再被监听时自动释放 final searchProvider = FutureProvider.autoDispose .family<List<Product>, String>((ref, query) async { // 防抖:取消之前的请求 ref.onDispose(() { // 清理资源 }); // 保持状态一段时间,避免反复创建 ref.keepAlive(); return searchProducts(query); });
6.7 Notifier(Riverpod 2.0 推荐)
dart // 使用 Notifier 管理复杂状态 class TodoList extends Notifier<List<Todo>> { @override List<Todo> build() => []; // 初始状态 void add(String title) { state = [ ...state, Todo(id: uuid(), title: title), ]; } void toggle(String id) { state = [ for (final todo in state) if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo, ]; } void remove(String id) { state = state.where((t) => t.id != id).toList(); } } final todoListProvider = NotifierProvider<TodoList, List<Todo>>( TodoList.new, ); // AsyncNotifier 处理异步 class AsyncTodoList extends AsyncNotifier<List<Todo>> { @override Future<List<Todo>> build() async { return fetchTodos(); } Future<void> add(String title) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await createTodo(title); return fetchTodos(); }); } }
🎯 第七章:BLoC / Cubit
7.1 概述
BLoC(Business Logic Component)是一种基于事件驱动和流(Stream)的架构模式。Cubit 是 BLoC 的简化版本,不需要定义事件类,直接通过方法调用来改变状态。
yaml # pubspec.yaml dependencies: flutter_bloc: ^8.1.3 bloc: ^8.1.2 equatable: ^2.0.5
7.2 Cubit(推荐入门)
Cubit 比 BLoC 更简洁,适合大部分场景。
dart // 定义状态 class CounterState extends Equatable { final int count; final bool isLoading; const CounterState({this.count = 0, this.isLoading = false}); CounterState copyWith({int? count, bool? isLoading}) { return CounterState( count: count ?? this.count, isLoading: isLoading ?? this.isLoading, ); } @override List<Object> get props => [count, isLoading]; } // 定义 Cubit class CounterCubit extends Cubit<CounterState> { CounterCubit() : super(const CounterState()); void increment() => emit(state.copyWith(count: state.count + 1)); void decrement() => emit(state.copyWith(count: state.count - 1)); void reset() => emit(const CounterState()); Future<void> incrementAsync() async { emit(state.copyWith(isLoading: true)); await Future.delayed(Duration(seconds: 1)); emit(state.copyWith(count: state.count + 1, isLoading: false)); } }
7.3 BlocProvider 和 BlocBuilder
dart // 提供 Cubit/BLoC class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (_) => CounterCubit(), child: MaterialApp(home: CounterPage()), ); } } // BlocBuilder: 根据状态重建 UI class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('BLoC 计数器')), body: Center( child: BlocBuilder<CounterCubit, CounterState>( // buildWhen: 精确控制何时重建 buildWhen: (prev, curr) => prev.count != curr.count, builder: (context, state) { if (state.isLoading) { return CircularProgressIndicator(); } return Text( '${state.count}', style: TextStyle(fontSize: 60), ); }, ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( heroTag: 'increment', onPressed: () => context.read<CounterCubit>().increment(), child: Icon(Icons.add), ), SizedBox(height: 8), FloatingActionButton( heroTag: 'decrement', onPressed: () => context.read<CounterCubit>().decrement(), child: Icon(Icons.remove), ), ], ), ); } }
7.4 BlocConsumer
BlocConsumer 结合了 BlocBuilder(构建 UI)和 BlocListener(监听副作用),适合需要同时响应状态变化并执行副作用的场景。
dart BlocConsumer<CounterCubit, CounterState>( // listener: 执行副作用(导航、弹窗、SnackBar 等) listenWhen: (prev, curr) => prev.count != curr.count, listener: (context, state) { if (state.count == 10) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('恭喜,到达 10!')), ); } }, // builder: 构建 UI buildWhen: (prev, curr) => prev.count != curr.count, builder: (context, state) { return Text('${state.count}', style: TextStyle(fontSize: 48)); }, )
7.5 完整 BLoC 示例(事件驱动)
dart // ===== 事件定义 ===== sealed class AuthEvent {} class LoginRequested extends AuthEvent { final String email; final String password; LoginRequested({required this.email, required this.password}); } class LogoutRequested extends AuthEvent {} // ===== 状态定义 ===== sealed class AuthState {} class AuthInitial extends AuthState {} class AuthLoading extends AuthState {} class AuthSuccess extends AuthState { final User user; AuthSuccess(this.user); } class AuthFailure extends AuthState { final String message; AuthFailure(this.message); } // ===== BLoC 定义 ===== class AuthBloc extends Bloc<AuthEvent, AuthState> { final AuthRepository _repo; AuthBloc(this._repo) : super(AuthInitial()) { on<LoginRequested>(_onLogin); on<LogoutRequested>(_onLogout); } Future<void> _onLogin( LoginRequested event, Emitter<AuthState> emit, ) async { emit(AuthLoading()); try { final user = await _repo.login(event.email, event.password); emit(AuthSuccess(user)); } catch (e) { emit(AuthFailure(e.toString())); } } Future<void> _onLogout( LogoutRequested event, Emitter<AuthState> emit, ) async { await _repo.logout(); emit(AuthInitial()); } } // ===== 使用 ===== BlocProvider( create: (context) => AuthBloc(AuthRepository()), child: BlocConsumer<AuthBloc, AuthState>( listener: (context, state) { if (state is AuthFailure) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message)), ); } }, builder: (context, state) { return switch (state) { AuthInitial() => LoginForm(), AuthLoading() => CircularProgressIndicator(), AuthSuccess(user: final u) => HomePage(user: u), AuthFailure() => LoginForm(), }; }, ), )
⚡ 第八章:GetX 简介
8.1 概述
GetX 是一个轻量但功能强大的 Flutter 解决方案,集成了状态管理、路由管理和依赖注入三大功能。使用简单,但在大型项目中的可维护性存在争议。
yaml # pubspec.yaml dependencies: get: ^4.6.6
8.2 响应式状态管理(Obs + Obx)
dart import 'package:get/get.dart'; // 定义控制器 class CounterController extends GetxController { // .obs 让变量变成响应式 var count = 0.obs; var name = 'Flutter'.obs; var items = <String>[].obs; void increment() => count++; void changeName(String n) => name.value = n; void addItem(String item) => items.add(item); } // 使用 class CounterPage extends StatelessWidget { final controller = Get.put(CounterController()); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Obx(() => Text( '${controller.count}', style: TextStyle(fontSize: 48), )), ), floatingActionButton: FloatingActionButton( onPressed: controller.increment, child: Icon(Icons.add), ), ); } }
8.3 简单状态管理(GetBuilder)
dart // 不使用响应式,手动调用 update() class SimpleController extends GetxController { int count = 0; void increment() { count++; update(); // 手动通知更新,类似 setState } } // GetBuilder 性能更好,不使用 Stream GetBuilder<SimpleController>( init: SimpleController(), builder: (ctrl) => Text('${ctrl.count}'), )
8.4 优缺点分析
优点
- 语法极其简洁,学习成本低
- 集成状态管理 + 路由 + 依赖注入
- 不需要 BuildContext
- 响应式更新,性能不错
- 快速开发小型项目
缺点
- "魔法"太多,隐式行为不透明
- 与 Flutter 设计哲学不一致
- 大型项目中可维护性存疑
- 依赖注入全局化,难以测试
- 社区有较大争议
📊 第九章:方案对比
9.1 综合对比表
| 方案 | 学习曲线 | 样板代码 | 可测试性 | 适合规模 | 依赖 Context |
|---|---|---|---|---|---|
| setState | 极低 | 极少 | 低 | 单组件 | 是 |
| InheritedWidget | 中等 | 多 | 中等 | 小型 | 是 |
| ValueNotifier | 低 | 少 | 中等 | 小型 | 是 |
| Provider | 低 | 少 | 高 | 中大型 | 是 |
| Riverpod | 中等 | 少 | 极高 | 中大型 | 否 |
| BLoC | 较高 | 多 | 极高 | 大型 | 是 |
| GetX | 极低 | 极少 | 低 | 小型 | 否 |
9.2 选择建议
个人项目 / 学习
setState + Provider 足够。先掌握基础,再探索其他方案。
中小型团队项目
推荐 Riverpod 或 Provider。Riverpod 类型更安全,Provider 生态更成熟。
大型企业项目
推荐 BLoC 或 Riverpod。BLoC 的事件驱动模型适合复杂业务流程,可追溯性强。
快速原型 / MVP
GetX 或 Provider。GetX 开发速度最快,但不推荐长期维护的项目使用。
9.3 关键决策因素
- 团队经验:选择团队最熟悉的方案,降低上手成本
- 项目规模:小项目不要过度工程化,大项目需要严谨的架构
- 测试需求:如果需要高测试覆盖率,选择 Riverpod 或 BLoC
- 异步处理:如果有大量异步操作,Riverpod 的 AsyncValue 体验最佳
- 可维护性:长期项目优先考虑代码可读性和社区支持度
💻 第十章:实践练习
练习 1:主题切换器(Provider)
使用 Provider 实现一个支持浅色/深色主题切换的应用:
- 创建
ThemeModel extends ChangeNotifier,包含themeMode属性 - 使用
ChangeNotifierProvider在顶层提供 ThemeModel - 在 MaterialApp 中根据 ThemeModel 设置
themeMode - 创建设置页面,使用
Consumer或context.watch显示当前主题 - 添加 Switch 组件切换主题,使用
context.read调用切换方法 - 扩展:支持三种模式(浅色、深色、跟随系统),使用 RadioListTile 选择
练习 2:购物车应用(Riverpod)
使用 Riverpod 构建一个完整的购物车功能:
- 定义
Product数据模型(id、name、price、imageUrl) - 创建
productsProvider(FutureProvider),模拟从 API 加载商品列表 - 创建
CartNotifier extends Notifier<List<CartItem>>,实现增/删/改数量 - 创建派生 Provider:
cartTotalProvider计算总价、cartCountProvider计算商品数 - 商品列表页:使用
AsyncValue.when处理加载/错误/成功状态 - 购物车页:展示已添加商品,支持修改数量和删除
- 在 AppBar 上用 Badge 显示购物车商品数量
练习 3:登录流程(BLoC)
使用 BLoC 模式实现完整的登录/注册流程:
- 定义事件:
LoginSubmitted、RegisterSubmitted、LogoutRequested - 定义状态:
AuthInitial、AuthLoading、AuthAuthenticated、AuthError - 实现
AuthBloc,处理各种事件,包含表单验证逻辑 - 登录页面使用
BlocConsumer:listener 处理错误提示和导航,builder 构建表单 - 主页使用
BlocBuilder根据认证状态显示不同内容 - 添加
BlocObserver在控制台打印所有状态变化,便于调试 - 编写单元测试:使用
blocTest验证事件到状态的转换逻辑