从 0 到 1:Flutter 状态管理实战 —— 打造高性能待办清单应用
状态管理的本质:状态管理的核心是 “状态与 UI 解耦”,让状态的修改和 UI 的更新分离,提高代码的可维护性。技术选型原则局部简单状态:优先使用setState;跨组件共享状态:使用 Provider(轻量、易上手);复杂业务逻辑:可考虑 Bloc/Riverpod 等更重型的状态管理方案。性能优化关键减少重建范围(Consumer/Selector);避免不必要的监听(listen: fals
欢迎大家加入[开源鸿蒙跨平台开发者社区](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 状态管理的底层逻辑,而非单纯 “复制粘贴” 代码。如果有任何问题,欢迎在评论区交流讨论!
更多推荐


所有评论(0)