第2章:第一个Flutter应用 —— 2.3 状态管理

2.3 状态管理

📚 核心知识点

  1. 三种状态管理方式
  2. 如何选择合适的管理方式
  3. 父子组件通信
  4. 混合管理的应用场景

💡 核心概念

什么是状态管理?

状态(State) 是指在应用运行过程中可以改变的数据。

状态管理 就是决定:

  • 状态应该存储在哪里?
  • 谁来管理这个状态?
  • 如何更新状态?

三种状态管理方式

graph TB A[状态管理] --> B[Widget自己管理] A --> C[父Widget管理] A --> D[混合管理] B --> B1[封装性好
适合UI效果] C --> C1[便于控制
适合用户数据] D --> D1[灵活性高
职责清晰] style A fill:#E1F5FE,stroke:#01579B style B fill:#E8F5E9,stroke:#2E7D32 style C fill:#FFF3E0,stroke:#E65100 style D fill:#F3E5F5,stroke:#4A148C

🎯 方式1:Widget管理自己的状态

适用场景

状态是内部实现细节

  • 动画进度
  • 高亮效果
  • 选中效果
  • 展开/折叠状态

示例:TapboxA

scala 复制代码
class TapboxA extends StatefulWidget {
  @override
  State<TapboxA> createState() => _TapboxAState();
}
​
class _TapboxAState extends State<TapboxA> {
  bool _active = false;  // ← 自己管理状态
  
  void _handleTap() {
    setState(() {
      _active = !_active;  // ← 自己更新状态
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        color: _active ? Colors.green : Colors.grey,
        child: Text(_active ? 'Active' : 'Inactive'),
      ),
    );
  }
}

优点

封装性好 - 外部不需要知道内部状态 ✅ 可复用性强 - 可以直接在多处使用 ✅ 使用简单 - TapboxA() 即可,无需传参

缺点

❌ 外部无法访问或控制状态 ❌ 不适合需要共享的状态


🎯 方式2:父Widget管理子Widget的状态

适用场景

状态是用户数据

  • 复选框的选中状态
  • 滑块的位置
  • 表单输入的值
  • 需要外部控制的状态

示例:TapboxB

scala 复制代码
// 父Widget
class ParentWidget extends StatefulWidget {
  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}
​
class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;  // ← 父组件管理状态
  
  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;  // ← 父组件更新状态
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return TapboxB(
      active: _active,           // ← 传递状态给子组件
      onChanged: _handleTapboxChanged,  // ← 传递回调
    );
  }
}
​
// 子Widget(无状态)
class TapboxB extends StatelessWidget {
  final bool active;
  final ValueChanged<bool> onChanged;
  
  const TapboxB({required this.active, required this.onChanged});
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => onChanged(!active),  // ← 通知父组件改变状态
      child: Container(
        color: active ? Colors.green : Colors.grey,
        child: Text(active ? 'Active' : 'Inactive'),
      ),
    );
  }
}

数据流向

sequenceDiagram participant 父组件 participant 子组件 父组件->>子组件: 传递 active 状态 Note over 子组件: 用户点击 子组件->>父组件: 调用 onChanged(新值) Note over 父组件: setState更新状态 父组件->>子组件: 传递新的 active 状态 Note over 子组件: UI更新

优点

父组件可以控制 - 方便统一管理 ✅ 适合表单场景 - 便于收集数据 ✅ 便于状态同步 - 多个子组件共享状态

缺点

❌ 子组件需要更多参数 ❌ 增加了父子组件的耦合


🎯 方式3:混合状态管理

适用场景

不同状态有不同的管理需求

  • active(用户数据) → 父组件管理
  • highlight(UI效果) → 子组件管理

示例:TapboxC

scala 复制代码
// 父Widget - 管理active状态
class ParentWidgetC extends StatefulWidget {
  @override
  State<ParentWidgetC> createState() => _ParentWidgetCState();
}
​
class _ParentWidgetCState extends State<ParentWidgetC> {
  bool _active = false;  // ← 父组件管理:active状态
  
  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return TapboxC(
      active: _active,
      onChanged: _handleTapboxChanged,
    );
  }
}
​
// 子Widget - 同时管理highlight状态
class TapboxC extends StatefulWidget {
  final bool active;
  final ValueChanged<bool> onChanged;
  
  const TapboxC({required this.active, required this.onChanged});
  
  @override
  State<TapboxC> createState() => _TapboxCState();
}
​
class _TapboxCState extends State<TapboxC> {
  bool _highlight = false;  // ← 子组件自己管理:highlight状态
  
  void _handleTapDown(TapDownDetails details) {
    setState(() {
      _highlight = true;  // ← 按下时高亮
    });
  }
  
  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _highlight = false;  // ← 抬起时取消高亮
    });
  }
  
  void _handleTap() {
    widget.onChanged(!widget.active);  // ← 通知父组件改变active
  }
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: _handleTapDown,
      onTapUp: _handleTapUp,
      onTap: _handleTap,
      child: Container(
        color: widget.active ? Colors.green : Colors.grey,
        decoration: BoxDecoration(
          border: _highlight ? Border.all(color: Colors.teal, width: 10) : null,
        ),
        child: Text(widget.active ? 'Active' : 'Inactive'),
      ),
    );
  }
}

状态职责划分

状态 管理者 原因
active 父组件 用户数据,需要外部访问
highlight 子组件 UI效果,内部实现细节

优点

职责清晰 - 各自管理适合的状态 ✅ 灵活性高 - 兼顾封装和控制 ✅ 性能优化 - highlight变化不影响父组件


📋 如何选择管理方式?

决策流程

flowchart TD A["这个状态是什么?"] -->|UI效果| B["Widget自己管理"] A -->|用户数据| C["需要外部访问吗?"] C -->|需要| D["父Widget管理"] C -->|不需要| B A -->|混合情况| E["混合管理"] style B fill:#E8F5E9,stroke:#2E7D32 style D fill:#FFF3E0,stroke:#E65100 style E fill:#F3E5F5,stroke:#4A148C

判断标准

问题 答案 建议方式
是UI效果(动画、高亮)? Widget自己管理
是用户数据(选中、输入)? 父Widget管理
需要多个Widget共享? 父Widget管理
外部需要控制这个状态? 父Widget管理
有多种状态需求不同? 混合管理

常见场景示例

less 复制代码
// ✅ Widget自己管理
AnimatedContainer(...)  // 动画状态
ExpansionTile(...)      // 展开/折叠状态
​
// ✅ 父Widget管理
Checkbox(value: _checked, onChanged: ...)  // 选中状态
Slider(value: _volume, onChanged: ...)     // 滑块值
TextField(controller: _controller)         // 输入内容
​
// ✅ 混合管理
// 按钮的pressed状态自己管理,但enabled状态由父组件管理
ElevatedButton(
  onPressed: _isEnabled ? _handleTap : null,  // 父组件控制
  // 按压效果由按钮内部管理
  child: Text('按钮'),
)

🌍 全局状态管理

什么时候需要全局状态管理?

当状态需要在多个不相关的Widget之间共享时:

  • 用户登录状态
  • 主题设置
  • 语言设置
  • 购物车数据

两种方案

1. 事件总线(Event Bus)

scss 复制代码
// 发布事件
eventBus.fire(LanguageChangedEvent('zh-CN'));
​
// 订阅事件
eventBus.on<LanguageChangedEvent>().listen((event) {
  setState(() {
    // 更新UI
  });
});

2. 状态管理库

常用库:

  • Provider - Flutter官方推荐
  • Riverpod - Provider的改进版
  • Bloc - 基于Stream的状态管理
  • GetX - 轻量级状态管理

Provider示例:

scala 复制代码
// 1. 定义状态类
class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  
  void increment() {
    _count++;
    notifyListeners();  // 通知监听者
  }
}
​
// 2. 提供状态
ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: MyApp(),
)
​
// 3. 使用状态
Consumer<CounterModel>(
  builder: (context, counter, child) {
    return Text('${counter.count}');
  },
)

对比:

方案 优点 缺点
事件总线 简单、解耦 难以调试、类型不安全
Provider 官方推荐、类型安全 需要学习
Bloc 架构清晰、可测试 代码量大
GetX 简单易用 不够规范

🎯 核心总结

  1. Widget自己管理 - UI效果、内部细节
  2. 父Widget管理 - 用户数据、需要控制
  3. 混合管理 - 不同状态不同需求
  4. 全局管理 - 跨组件共享状态(Provider等)

选择原则:

  • 用户数据 → 父管理
  • UI效果 → 自己管理
  • 共享状态 → 共同父管理

📝 常见问题

Q1: 为什么不能直接修改状态,必须用setState?

A:

scss 复制代码
// ❌ 错误:直接修改
_count++;  // 状态改了,但UI不更新
​
// ✅ 正确:使用setState
setState(() {
  _count++;  // Flutter知道要更新UI
});

原理: setState()告诉Flutter框架状态变了,需要重新build UI。

Q2: 父子组件通信的ValueChanged是什么?

A:

dart 复制代码
typedef ValueChanged<T> = void Function(T value);

它是一个回调函数类型,用于从子组件向父组件传递值。

scss 复制代码
// 父组件
void _handleChanged(bool newValue) {
  setState(() => _active = newValue);
}
​
// 传给子组件
TapboxB(onChanged: _handleChanged)
​
// 子组件调用
widget.onChanged(true);  // 触发父组件的_handleChanged(true)

Q3: 混合管理中,子组件如何访问父组件传来的状态?

A: 使用 widget.xxx

scala 复制代码
class _TapboxCState extends State<TapboxC> {
  @override
  Widget build(BuildContext context) {
    // ✅ 通过widget访问父组件传来的属性
    return Container(
      color: widget.active ? Colors.green : Colors.grey,
    );
  }
}

原理: State对象持有Widget的引用,可以通过widget访问。

Q4: 什么时候应该用全局状态管理?

A:

需要:

  • 登录状态(多个页面都要用)
  • 主题/语言(全局生效)
  • 购物车(跨页面共享)

不需要:

  • 表单输入(局部数据)
  • 按钮按压(临时效果)
  • 列表滚动位置(组件内部)

🎓 跟着做练习

练习1:改进计数器 ⭐

目标: 把2.1的计数器改成父组件管理状态

提示:

  1. 创建父组件管理_counter
  2. 创建子组件显示计数
  3. 用回调函数通知父组件
scala 复制代码
// 父组件
class ParentCounter extends StatefulWidget {
  @override
  State<ParentCounter> createState() => _ParentCounterState();
}
​
class _ParentCounterState extends State<ParentCounter> {
  int _counter = 0;
​
  void _increment() {
    setState(() => _counter++);
  }
​
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('计数:$_counter'),
        CounterButton(onPressed: _increment),
      ],
    );
  }
}
​
// 子组件
class CounterButton extends StatelessWidget {
  final VoidCallback onPressed;
  
  const CounterButton({required this.onPressed});
  
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text('+1'),
    );
  }
}

练习2:创建一个开关组件 ⭐⭐

目标: 创建一个自己管理状态的开关组件

要求:

  • 点击切换开/关
  • 开:绿色,显示"ON"
  • 关:灰色,显示"OFF"
scala 复制代码
class MySwitch extends StatefulWidget {
  @override
  State<MySwitch> createState() => _MySwitchState();
}
​
class _MySwitchState extends State<MySwitch> {
  bool _isOn = false;
​
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _isOn = !_isOn;
        });
      },
      child: Container(
        width: 60,
        height: 30,
        decoration: BoxDecoration(
          color: _isOn ? Colors.green : Colors.grey,
          borderRadius: BorderRadius.circular(15),
        ),
        child: Center(
          child: Text(
            _isOn ? 'ON' : 'OFF',
            style: TextStyle(color: Colors.white),
          ),
        ),
      ),
    );
  }
}

练习3:购物车数量选择器 ⭐⭐⭐

目标: 创建一个商品数量选择器(混合管理)

要求:

  • 数量(count)由父组件管理
  • 按钮按压高亮由子组件管理
  • 有 + 和 - 按钮
scala 复制代码
// 父组件
class ShoppingCart extends StatefulWidget {
  @override
  State<ShoppingCart> createState() => _ShoppingCartState();
}
​
class _ShoppingCartState extends State<ShoppingCart> {
  int _count = 1;
​
  @override
  Widget build(BuildContext context) {
    return QuantitySelector(
      count: _count,
      onChanged: (newCount) {
        setState(() {
          _count = newCount.clamp(0, 99);  // 限制范围
        });
      },
    );
  }
}
​
// 子组件
class QuantitySelector extends StatefulWidget {
  final int count;
  final ValueChanged<int> onChanged;
  
  const QuantitySelector({required this.count, required this.onChanged});
  
  @override
  State<QuantitySelector> createState() => _QuantitySelectorState();
}
​
class _QuantitySelectorState extends State<QuantitySelector> {
  bool _minusHighlight = false;
  bool _plusHighlight = false;
​
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        GestureDetector(
          onTapDown: (_) => setState(() => _minusHighlight = true),
          onTapUp: (_) => setState(() => _minusHighlight = false),
          onTapCancel: () => setState(() => _minusHighlight = false),
          onTap: () => widget.onChanged(widget.count - 1),
          child: Container(
            width: 40,
            height: 40,
            decoration: BoxDecoration(
              color: _minusHighlight ? Colors.red[300] : Colors.red,
              borderRadius: BorderRadius.circular(8),
            ),
            child: Icon(Icons.remove, color: Colors.white),
          ),
        ),
        Container(
          width: 60,
          alignment: Alignment.center,
          child: Text('${widget.count}', style: TextStyle(fontSize: 20)),
        ),
        GestureDetector(
          onTapDown: (_) => setState(() => _plusHighlight = true),
          onTapUp: (_) => setState(() => _plusHighlight = false),
          onTapCancel: () => setState(() => _plusHighlight = false),
          onTap: () => widget.onChanged(widget.count + 1),
          child: Container(
            width: 40,
            height: 40,
            decoration: BoxDecoration(
              color: _plusHighlight ? Colors.green[300] : Colors.green,
              borderRadius: BorderRadius.circular(8),
            ),
            child: Icon(Icons.add, color: Colors.white),
          ),
        ),
      ],
    );
  }
}

参考: 《Flutter实战·第二版》2.3节

相关推荐
行走的陀螺仪4 小时前
Flutter 开发环境配置教程
android·前端·flutter·ios
QuantumLeap丶6 小时前
《Flutter全栈开发实战指南:从零到高级》- 12 -状态管理Bloc
android·flutter·ios
前端好多云6 小时前
从一个带并发数限制的请求深入 Dart 的 Future
flutter·dart
星释15 小时前
鸿蒙Flutter三方库适配指南:09.版本升级适配
flutter·华为·harmonyos
bing.shao19 小时前
Flutter 与 Native的比较
flutter
旧时光_1 天前
第2章:第一个Flutter应用 —— 2.4 路由管理
flutter
旧时光_1 天前
第2章:第一个Flutter应用 —— 2.2 Widget简介
flutter
Bryce李小白1 天前
Flutter provide框架内部实现原理刨析
flutter