欢迎大家加入[开源鸿蒙跨平台开发者社区](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. 资源管理TextField 的 TextEditingController 是持有资源的对象,必须在 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.of 或 Consumer 获取。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 状态管理的底层逻辑,而非单纯 “复制粘贴” 代码。如果有任何问题,欢迎在评论区交流讨论!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐