news 2026/4/23 5:06:00

从 0 到 1:Flutter 状态管理实战 —— 打造高性能待办清单应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从 0 到 1:Flutter 状态管理实战 —— 打造高性能待办清单应用

欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。

在 Flutter 开发中,状态管理始终是核心且容易让开发者困惑的话题。很多初学者会陷入 “setState 够用吗?”“Provider 和 Bloc 该选哪个?” 的纠结中。本文将以一个高性能待办清单(Todo List)应用为例,从基础的 setState 到进阶的 Provider 状态管理,一步步拆解 Flutter 状态管理的核心逻辑,结合完整的代码实现和深度解析,让你不仅能写出可运行的代码,更能理解背后的设计思想。

一、项目背景与技术选型

待办清单是典型的状态驱动型应用:用户添加、删除、标记完成待办项,界面需要实时响应状态变化。我们将分两个阶段实现:

  1. 基础版:使用 setState 管理局部状态,理解状态更新的基本原理;
  2. 进阶版:使用 Provider + ChangeNotifier 实现全局状态管理,解决跨组件状态共享问题;
  3. 性能优化:通过 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(); } }

代码深度解析

  1. 数据模型设计TodoItem类封装了待办项的核心属性,其中isCompleted是可变状态,用于标记是否完成。
  2. 状态管理核心_todoList是待办列表的数据源,属于 Widget 的核心状态。所有修改状态的方法(_addTodo/_deleteTodo/_toggleTodoStatus)都包裹在setState中 ——setState会触发 Widget 的build方法重新执行,从而更新 UI。
  3. 资源管理TextFieldTextEditingController是持有资源的对象,必须在dispose方法中释放,避免内存泄漏。
  4. UI 渲染逻辑ListView.builder是按需渲染列表的核心组件,仅构建当前可视区域的 Item,避免一次性渲染大量数据导致性能问题。

基础版的局限性

虽然setState实现简单,但存在明显短板:

  • 状态只能在当前 Widget 内部共享,若需要在多个页面(如待办详情页)修改状态,会导致组件耦合严重;
  • 每次调用setState会重建整个 Widget 树(当前 Widget 及其子 Widget),即使只有一个待办项状态变化,整个列表都会重新构建,性能损耗随列表长度增加而加剧。

三、进阶版:Provider 实现全局状态管理

核心思路

Provider 是基于 InheritedWidget 实现的状态管理库,核心思想是 “状态上提 + 依赖注入”:

  1. 将共享状态抽离到独立的 ViewModel 类(继承ChangeNotifier);
  2. 通过ChangeNotifierProvider将 ViewModel 注入到 Widget 树中;
  3. 子 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); }, ), ); } }

核心知识点解析

  1. ChangeNotifier:ViewModel 继承此类,通过notifyListeners()通知所有监听者状态变化。相比setState,它实现了 “精准通知”—— 只有依赖该状态的 Widget 会重建。
  2. ChangeNotifierProvider:将 ViewModel 注入到 Widget 树中,使其子 Widget 可以通过Provider.ofConsumer获取。create方法用于创建 ViewModel 实例。
  3. Consumer 的使用Consumer<TodoViewModel>会监听 ViewModel 的状态变化,仅当状态变化时重建其内部的 Widget(本例中是 ListView),而非整个 TodoHomePage。
  4. listen: false:当仅需要调用 ViewModel 的方法(不监听状态)时,设置listen: false,避免不必要的监听和重建。
  5. 状态封装: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 优化原理

  1. selector 函数:从 ViewModel 中提取当前 Widget 真正依赖的状态(本例中是当前 todo 的isCompleted),而非整个todoList
  2. shouldRebuild 函数:自定义重建条件,仅当提取的状态发生变化时,才重建 Widget。
  3. child 参数:将不随状态变化的 Widget(如删除按钮)作为 child 传入,复用该 Widget,避免不必要的重建。

通过 Selector 优化后,修改一个待办项的完成状态,只有该待办项的 Widget 会重建,其他项和 ListView 本身都不会重建,大幅提升列表性能。

五、总结与扩展

核心收获

  1. 状态管理的本质:状态管理的核心是 “状态与 UI 解耦”,让状态的修改和 UI 的更新分离,提高代码的可维护性。
  2. 技术选型原则
    • 局部简单状态:优先使用setState
    • 跨组件共享状态:使用 Provider(轻量、易上手);
    • 复杂业务逻辑:可考虑 Bloc/Riverpod 等更重型的状态管理方案。
  3. 性能优化关键
    • 减少重建范围(Consumer/Selector);
    • 避免不必要的监听(listen: false);
    • 复用静态 Widget(child 参数)。

扩展方向

  1. 持久化:结合shared_preferences将待办数据保存到本地,重启应用不丢失;
  2. 动画:添加待办项 / 删除待办项的过渡动画,提升用户体验;
  3. 分类:增加待办分类(工作 / 生活 / 学习),扩展状态管理逻辑;
  4. 国际化:适配多语言,结合 Provider 管理语言状态。

本文的代码经过实际运行验证,覆盖了 Flutter 状态管理的核心场景和优化技巧。希望通过这个待办清单的案例,你能真正理解 Flutter 状态管理的底层逻辑,而非单纯 “复制粘贴” 代码。如果有任何问题,欢迎在评论区交流讨论!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 5:06:00

GPT-SoVITS语音克隆实战:1分钟数据训练专属TTS模型

GPT-SoVITS语音克隆实战&#xff1a;1分钟数据训练专属TTS模型 在智能语音助手、虚拟偶像和有声内容爆发的今天&#xff0c;我们越来越不满足于千篇一律的“机器音”。用户想要的是自己的声音——能为孩子朗读睡前故事的母亲的声音&#xff0c;能在直播中代班的主播声音&#x…

作者头像 李华
网站建设 2026/4/22 1:10:25

使用Miniconda镜像降低GPU算力资源浪费的5个技巧

使用Miniconda镜像降低GPU算力资源浪费的5个技巧 在现代AI研发环境中&#xff0c;一个常见的尴尬场景是&#xff1a;你提交了一个训练任务&#xff0c;满怀期待地等待模型收敛&#xff0c;结果几分钟后收到告警——“torch not found”或“CUDA version mismatch”。更糟的是&…

作者头像 李华
网站建设 2026/4/20 4:50:44

基于gpt-oss-20b的轻量级大模型应用:低延迟响应的秘密

基于gpt-oss-20b的轻量级大模型应用&#xff1a;低延迟响应的秘密 在生成式AI席卷全球的今天&#xff0c;越来越多企业开始尝试将大语言模型&#xff08;LLM&#xff09;集成到产品中。但现实往往令人却步——高昂的API调用成本、动辄数百毫秒的响应延迟、以及对用户数据隐私的…

作者头像 李华
网站建设 2026/4/18 9:54:30

[watevrCTF 2019]Voting Machine 1

第一次打CTF——PWN篇学习笔记12int __fastcall main(int argc, const char **argv, const char **envp) {_BYTE v4[2]; // [rspEh] [rbp-2h] BYREF ​signal(14, sig);alarm(0x28u);puts("Hello and welcome to \x1B[3mour\x1B[23m voting application!");puts(&quo…

作者头像 李华
网站建设 2026/4/11 16:54:47

Java毕设选题推荐:基于Springboot+Vue的古风生活文化分享体验交流网站基于springboot古风生活体验交流网站的设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/22 18:40:28

SQLPad查询缓存配置完全指南:如何让重复查询速度提升10倍

SQLPad查询缓存配置完全指南&#xff1a;如何让重复查询速度提升10倍 【免费下载链接】sqlpad Web-based SQL editor. Legacy project in maintenance mode. 项目地址: https://gitcode.com/gh_mirrors/sq/sqlpad 还在为每次执行相同SQL查询都要等待几秒钟而烦恼吗&…

作者头像 李华