欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。
在 Flutter 开发中,状态管理始终是核心且容易让开发者困惑的话题。很多初学者会陷入 “setState 够用吗?”“Provider 和 Bloc 该选哪个?” 的纠结中。本文将以一个高性能待办清单(Todo List)应用为例,从基础的 setState 到进阶的 Provider 状态管理,一步步拆解 Flutter 状态管理的核心逻辑,结合完整的代码实现和深度解析,让你不仅能写出可运行的代码,更能理解背后的设计思想。
一、项目背景与技术选型
待办清单是典型的状态驱动型应用:用户添加、删除、标记完成待办项,界面需要实时响应状态变化。我们将分两个阶段实现:
- 基础版:使用 setState 管理局部状态,理解状态更新的基本原理;
- 进阶版:使用 Provider + ChangeNotifier 实现全局状态管理,解决跨组件状态共享问题;
- 性能优化:通过 Selector 减少不必要的重建,提升应用性能。
技术栈:Flutter 3.16+、Dart 3.2+、Provider 6.1+(主流且轻量的状态管理库)。
二、基础版:setState 实现局部状态管理
核心思路
setState 是 Flutter 最基础的状态管理方式,适用于单一 Widget 内的状态变化。我们先实现一个极简版 Todo 应用,包含 “添加待办”“删除待办”“标记完成” 三个核心功能。
完整代码实现
dart
import 'package:flutter/material.dart'; void main() => runApp(const TodoApp()); // 应用根组件 class TodoApp extends StatelessWidget { const TodoApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Todo 基础版', theme: ThemeData(primarySwatch: Colors.blue), home: const TodoHomePage(), ); } } // 待办项数据模型 class TodoItem { final String id; // 唯一标识 final String content; // 待办内容 bool isCompleted; // 是否完成(可变状态) TodoItem({ required this.id, required this.content, this.isCompleted = false, }); } // 待办主页(有状态组件) class TodoHomePage extends StatefulWidget { const TodoHomePage({super.key}); @override State<TodoHomePage> createState() => _TodoHomePageState(); } class _TodoHomePageState extends State<TodoHomePage> { // 待办列表数据源(核心状态) final List<TodoItem> _todoList = []; // 输入框控制器 final TextEditingController _inputController = TextEditingController(); // 添加待办项方法 void _addTodo() { final content = _inputController.text.trim(); if (content.isEmpty) return; // 空内容不添加 setState(() { // 生成唯一ID(简化版,实际项目可使用uuid库) final id = DateTime.now().millisecondsSinceEpoch.toString(); _todoList.add(TodoItem(id: id, content: content)); _inputController.clear(); // 清空输入框 }); } // 删除待办项方法 void _deleteTodo(String id) { setState(() { _todoList.removeWhere((todo) => todo.id == id); }); } // 切换待办完成状态 void _toggleTodoStatus(String id) { setState(() { final todo = _todoList.firstWhere((todo) => todo.id == id); todo.isCompleted = !todo.isCompleted; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('待办清单(setState版)')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ // 输入框 + 添加按钮 Row( children: [ Expanded( child: TextField( controller: _inputController, decoration: const InputDecoration( hintText: '请输入待办内容', border: OutlineInputBorder(), ), ), ), const SizedBox(width: 10), ElevatedButton( onPressed: _addTodo, child: const Text('添加'), ), ], ), const SizedBox(height: 20), // 待办列表 Expanded( child: ListView.builder( itemCount: _todoList.length, itemBuilder: (context, index) { final todo = _todoList[index]; return ListTile( leading: Checkbox( value: todo.isCompleted, onChanged: (_) => _toggleTodoStatus(todo.id), ), title: Text( todo.content, style: TextStyle( decoration: todo.isCompleted ? TextDecoration.lineThrough : TextDecoration.none, color: todo.isCompleted ? Colors.grey : Colors.black, ), ), trailing: IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => _deleteTodo(todo.id), ), ); }, ), ), ], ), ), ); } // 释放控制器资源 @override void dispose() { _inputController.dispose(); super.dispose(); } }代码深度解析
- 数据模型设计:
TodoItem类封装了待办项的核心属性,其中isCompleted是可变状态,用于标记是否完成。 - 状态管理核心:
_todoList是待办列表的数据源,属于 Widget 的核心状态。所有修改状态的方法(_addTodo/_deleteTodo/_toggleTodoStatus)都包裹在setState中 ——setState会触发 Widget 的build方法重新执行,从而更新 UI。 - 资源管理:
TextField的TextEditingController是持有资源的对象,必须在dispose方法中释放,避免内存泄漏。 - UI 渲染逻辑:
ListView.builder是按需渲染列表的核心组件,仅构建当前可视区域的 Item,避免一次性渲染大量数据导致性能问题。
基础版的局限性
虽然setState实现简单,但存在明显短板:
- 状态只能在当前 Widget 内部共享,若需要在多个页面(如待办详情页)修改状态,会导致组件耦合严重;
- 每次调用
setState会重建整个 Widget 树(当前 Widget 及其子 Widget),即使只有一个待办项状态变化,整个列表都会重新构建,性能损耗随列表长度增加而加剧。
三、进阶版:Provider 实现全局状态管理
核心思路
Provider 是基于 InheritedWidget 实现的状态管理库,核心思想是 “状态上提 + 依赖注入”:
- 将共享状态抽离到独立的 ViewModel 类(继承
ChangeNotifier); - 通过
ChangeNotifierProvider将 ViewModel 注入到 Widget 树中; - 子 Widget 通过
Consumer/Provider.of获取 ViewModel,监听状态变化并更新 UI。
步骤 1:引入 Provider 依赖
在pubspec.yaml中添加:
yaml
dependencies: flutter: sdk: flutter provider: ^6.1.1执行flutter pub get安装依赖。
步骤 2:实现 TodoViewModel(状态管理核心)
dart
import 'package:flutter/foundation.dart'; class TodoItem { final String id; final String content; bool isCompleted; TodoItem({ required this.id, required this.content, this.isCompleted = false, }); } // 待办状态管理类(ViewModel) class TodoViewModel extends ChangeNotifier { // 私有数据源 final List<TodoItem> _todoList = []; // 对外暴露只读的列表(避免外部直接修改) List<TodoItem> get todoList => List.unmodifiable(_todoList); // 添加待办 void addTodo(String content) { if (content.isEmpty) return; final id = DateTime.now().millisecondsSinceEpoch.toString(); _todoList.add(TodoItem(id: id, content: content)); // 通知监听者(Consumer)状态变化,触发UI更新 notifyListeners(); } // 删除待办 void deleteTodo(String id) { _todoList.removeWhere((todo) => todo.id == id); notifyListeners(); } // 切换完成状态 void toggleTodoStatus(String id) { final todo = _todoList.firstWhere((todo) => todo.id == id); todo.isCompleted = !todo.isCompleted; notifyListeners(); } // 清空所有待办(扩展功能) void clearAll() { _todoList.clear(); notifyListeners(); } }步骤 3:重构 UI 层(使用 Provider)
dart
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() => runApp( // 将ViewModel注入到根Widget,全局可访问 ChangeNotifierProvider( create: (context) => TodoViewModel(), child: const TodoApp(), ), ); class TodoApp extends StatelessWidget { const TodoApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Todo 进阶版', theme: ThemeData(primarySwatch: Colors.blue), home: const TodoHomePage(), ); } } class TodoHomePage extends StatelessWidget { const TodoHomePage({super.key}); @override Widget build(BuildContext context) { // 获取输入框控制器(StatelessWidget中使用late初始化) late final TextEditingController inputController = TextEditingController(); return Scaffold( appBar: AppBar( title: const Text('待办清单(Provider版)'), actions: [ // 清空所有待办按钮 TextButton( onPressed: () { // 获取ViewModel并调用方法 Provider.of<TodoViewModel>(context, listen: false).clearAll(); }, child: const Text( '清空', style: TextStyle(color: Colors.white), ), ), ], ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ // 输入框 + 添加按钮 Row( children: [ Expanded( child: TextField( controller: inputController, decoration: const InputDecoration( hintText: '请输入待办内容', border: OutlineInputBorder(), ), ), ), const SizedBox(width: 10), ElevatedButton( onPressed: () { final content = inputController.text.trim(); // listen: false 表示不监听状态变化,仅获取ViewModel Provider.of<TodoViewModel>(context, listen: false) .addTodo(content); inputController.clear(); }, child: const Text('添加'), ), ], ), const SizedBox(height: 20), // 待办列表(使用Consumer监听状态变化) Expanded( child: Consumer<TodoViewModel>( builder: (context, viewModel, child) { // 仅当todoList变化时,重建Listview return ListView.builder( itemCount: viewModel.todoList.length, itemBuilder: (context, index) { final todo = viewModel.todoList[index]; return TodoItemWidget(todo: todo); }, ); }, ), ), ], ), ), ); } } // 抽离待办项Widget,进一步解耦 class TodoItemWidget extends StatelessWidget { final TodoItem todo; const TodoItemWidget({super.key, required this.todo}); @override Widget build(BuildContext context) { return ListTile( leading: Checkbox( value: todo.isCompleted, onChanged: (_) { Provider.of<TodoViewModel>(context, listen: false) .toggleTodoStatus(todo.id); }, ), title: Text( todo.content, style: TextStyle( decoration: todo.isCompleted ? TextDecoration.lineThrough : TextDecoration.none, color: todo.isCompleted ? Colors.grey : Colors.black, ), ), trailing: IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () { Provider.of<TodoViewModel>(context, listen: false) .deleteTodo(todo.id); }, ), ); } }核心知识点解析
- ChangeNotifier:ViewModel 继承此类,通过
notifyListeners()通知所有监听者状态变化。相比setState,它实现了 “精准通知”—— 只有依赖该状态的 Widget 会重建。 - ChangeNotifierProvider:将 ViewModel 注入到 Widget 树中,使其子 Widget 可以通过
Provider.of或Consumer获取。create方法用于创建 ViewModel 实例。 - Consumer 的使用:
Consumer<TodoViewModel>会监听 ViewModel 的状态变化,仅当状态变化时重建其内部的 Widget(本例中是 ListView),而非整个 TodoHomePage。 - listen: false:当仅需要调用 ViewModel 的方法(不监听状态)时,设置
listen: false,避免不必要的监听和重建。 - 状态封装:ViewModel 将状态(
_todoList)私有化,对外暴露只读的todoList和修改状态的方法,保证状态修改的可控性(单一数据源原则)。
四、性能优化:使用 Selector 减少重建
即使使用了 Provider,默认的 Consumer 仍会在 ViewModel 的任何状态变化时重建整个 ListView。例如,修改一个待办项的完成状态,整个列表都会重新构建。我们可以通过Selector优化这一问题。
优化后的 TodoItemWidget
dart
class TodoItemWidget extends StatelessWidget { final TodoItem todo; const TodoItemWidget({super.key, required this.todo}); @override Widget build(BuildContext context) { return Selector<TodoViewModel, bool>( // 选择器:仅监听当前todo的isCompleted状态 selector: (context, viewModel) { final targetTodo = viewModel.todoList.firstWhere((t) => t.id == todo.id); return targetTodo.isCompleted; }, // 重建条件:仅当isCompleted变化时才重建当前Item shouldRebuild: (previous, next) => previous != next, builder: (context, isCompleted, child) { return ListTile( leading: Checkbox( value: isCompleted, onChanged: (_) { Provider.of<TodoViewModel>(context, listen: false) .toggleTodoStatus(todo.id); }, ), title: Text( todo.content, style: TextStyle( decoration: isCompleted ? TextDecoration.lineThrough : TextDecoration.none, color: isCompleted ? Colors.grey : Colors.black, ), ), trailing: child, // 静态Widget(删除按钮)复用,不重建 ); }, // 静态子Widget:删除按钮不会随状态变化重建 child: IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () { Provider.of<TodoViewModel>(context, listen: false) .deleteTodo(todo.id); }, ), ); } }Selector 优化原理
- selector 函数:从 ViewModel 中提取当前 Widget 真正依赖的状态(本例中是当前 todo 的
isCompleted),而非整个todoList。 - shouldRebuild 函数:自定义重建条件,仅当提取的状态发生变化时,才重建 Widget。
- child 参数:将不随状态变化的 Widget(如删除按钮)作为 child 传入,复用该 Widget,避免不必要的重建。
通过 Selector 优化后,修改一个待办项的完成状态,只有该待办项的 Widget 会重建,其他项和 ListView 本身都不会重建,大幅提升列表性能。
五、总结与扩展
核心收获
- 状态管理的本质:状态管理的核心是 “状态与 UI 解耦”,让状态的修改和 UI 的更新分离,提高代码的可维护性。
- 技术选型原则:
- 局部简单状态:优先使用
setState; - 跨组件共享状态:使用 Provider(轻量、易上手);
- 复杂业务逻辑:可考虑 Bloc/Riverpod 等更重型的状态管理方案。
- 局部简单状态:优先使用
- 性能优化关键:
- 减少重建范围(Consumer/Selector);
- 避免不必要的监听(listen: false);
- 复用静态 Widget(child 参数)。
扩展方向
- 持久化:结合
shared_preferences将待办数据保存到本地,重启应用不丢失; - 动画:添加待办项 / 删除待办项的过渡动画,提升用户体验;
- 分类:增加待办分类(工作 / 生活 / 学习),扩展状态管理逻辑;
- 国际化:适配多语言,结合 Provider 管理语言状态。
本文的代码经过实际运行验证,覆盖了 Flutter 状态管理的核心场景和优化技巧。希望通过这个待办清单的案例,你能真正理解 Flutter 状态管理的底层逻辑,而非单纯 “复制粘贴” 代码。如果有任何问题,欢迎在评论区交流讨论!