第十七讲 主流状态管理方案

0 阅读5分钟

前言:

主流的状态管理,一般用这几个管理组件,可以看看学学,当然,vibecoding时,要做好限制工作,不然会发疯的,最近国产模型,我个人感觉deepseek体感效果最好,比花了大价钱营销推广自己多牛的kimi、GLM明显强上一个档次,当然,仅限于这个时间段的国内模型。

一、总览

本讲的核心目标是帮助你理解 Flutter 中状态管理的本质,掌握从基础到主流的各类状态管理方案(Provider/ChangeNotifier、Riverpod、Bloc/Cubit),并学会根据实际场景划分全局状态局部状态,最终能够在项目中选择最合适的状态管理方案并落地实现。

状态管理的核心痛点:Flutter 中 Widget 状态分散在各个组件中,跨组件/跨页面共享状态时会出现代码耦合、数据流向混乱、维护成本高的问题。本章节通过系统化讲解主流方案,解决「状态该存在哪、该如何传递、该如何更新」的核心问题。

所有状态管理方案都基于「单一数据源 + 观察者模式」,底层依赖 InheritedWidget(Provider/Riverpod)或 Stream(Bloc)实现状态传递和更新;

方案选择

  • 简单场景/快速开发:Provider/ChangeNotifier;
  • 无上下文依赖/类型安全:Riverpod;
  • 复杂业务/可测试性:Bloc/Cubit;

作用域划分:全局状态在 App 根节点初始化,局部状态在页面/组件层级初始化,最小化状态作用域可减少性能损耗。

最佳实践

  • 优先使用「局部状态」,仅必要数据设为全局;

  • Riverpod 是 Provider 的升级版,新项目建议优先选择;

  • Bloc 适合团队协作/复杂业务,Cubit 适合简单业务;

  • 避免过度封装,状态管理的核心是「清晰的数据流向」而非「炫技」。

  • 核心模式:所有状态管理方案都遵循「单一数据源 + 观察者模式」,区别仅在于「通知方式」和「代码组织形式」。
  • InheritedWidget:Provider/Riverpod 的底层基础,实现跨 Widget 树传递数据(无需手动层层传递)。
  • 事件驱动:Bloc 的核心,将「状态修改动作」和「状态本身」解耦,通过 Event → State 的单向流管理状态。
  • 作用域划分:全局状态(如用户登录信息)需在 App 根节点初始化,局部状态(如购物车弹窗状态)仅在对应页面层级初始化。

二、各方案详解

2.1 基础方案:Provider + ChangeNotifier

Provider + ChangeNotifier 是 Flutter 中最常用的状态管理组合方案。它们共同解决了一个核心问题:如何让多个 Widget 共享和响应数据变化。

核心属性/类
类/属性作用
ChangeNotifier状态基类,通过 notifyListeners() 通知监听者状态变更
ChangeNotifierProvider提供状态的 Widget,子组件可获取状态
Consumer监听状态变更并重建 UI 的 Widget
Provider.of<T>()直接获取状态(需指定 listen: true/false
基础案例(计数器)

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// 1. 定义状态管理类
class CounterNotifier extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  // 状态修改方法
  void increment() {
    _count++;
    notifyListeners(); // 通知UI更新
  }

  void decrement() {
    _count--;
    notifyListeners();
  }
}

// 2. 使用 Provider 包裹页面
class ProviderCounterPage extends StatelessWidget {
  const ProviderCounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CounterNotifier(), // 创建状态实例
      child: Scaffold(
        appBar: AppBar(title: Text("Provider 计数器")),
        body: Center(
          child: Consumer<CounterNotifier>( // 监听状态变更
            builder: (context, counter, child) {
              return Text(
                "当前计数:${counter.count}",
                style: TextStyle(fontSize: 24),
              );
            },
          ),
        ),
        floatingActionButton: Builder(
          builder: (context) {
            return Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                FloatingActionButton(
                  onPressed: () {
                    Provider.of<CounterNotifier>(context, listen: false).decrement();
                  },
                  child: Icon(Icons.remove),
                ),
                SizedBox(width: 10),
                FloatingActionButton(
                  onPressed: () {
                    Provider.of<CounterNotifier>(context, listen: false).increment();
                  },
                  child: Icon(Icons.add),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

// 7. 入口函数
void main() {
  runApp(MaterialApp(
    title: "Provider 计数器",
    home: ProviderCounterPage(),
  ));

}

注意事项
  1. notifyListeners() 必须在状态修改后调用,否则 UI 不会更新
  2. Provider.of<T>(context, listen: false) 用于修改状态(不监听),listen: true 用于监听状态(会重建 Widget),要注意context需要是监听组件的context,别引用错了造成监听失效
  3. 局部状态建议用 Consumer 缩小重建范围,避免整个页面重建;
  4. 全局状态需在 MaterialApp 外层初始化(如 MultiProvider)。

2.2 主流方案1:Riverpod(Provider 升级版)

Riverpod 是 Provider 的改进版本,由同一作者开发,解决了 Provider 的诸多痛点,是目前 Flutter 社区最受欢迎的状态管理方案之一。

核心属性/类
类/属性作用
Provider只读状态提供者(不可变数据)
StateProvider可读写状态提供者(简单状态)
NotifierProvider复杂状态提供者(对应自定义 Notifier)
ref核心对象,用于读取/监听/修改状态
Consumer/ref.watch()监听状态变更
基础案例(计数器)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';

// 1. 定义状态(全局/局部均可)
final counterProvider = StateProvider<int>((ref) => 0);

// 2. Riverpod 页面(需用 ProviderScope 包裹 App)
class RiverpodCounterPage extends ConsumerWidget {
  const RiverpodCounterPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听状态
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: Text("Riverpod 计数器")),
      body: Center(
        child: Text(
          "当前计数:$count",
          style: TextStyle(fontSize: 24),
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              // 修改状态
              ref.read(counterProvider.notifier).state--;
            },
            child: Icon(Icons.remove),
          ),
          SizedBox(width: 10),
          FloatingActionButton(
            onPressed: () {
              ref.read(counterProvider.notifier).state++;
            },
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

// App 入口需包裹 ProviderScope
void main() {
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget{
  const MyApp({super.key});

  @override
  Widget build(BuildContext context){
    return MaterialApp(
        home: RiverpodCounterPage()
    );
  }
}

注意事项
  1. 必须用 ProviderScope 包裹整个 App(全局状态)或某个页面(局部状态);
  2. ref.watch() 只能在 build 方法中使用,异步场景用 ref.listen()
  3. 复杂状态建议用 NotifierProvider + 自定义 Notifier 类;
  4. 无上下文依赖,可在任意地方(如工具类)读取/修改状态。

2.3 主流方案2:Bloc/Cubit

核心属性/类
类/属性作用
Cubit简化版 Bloc,仅管理状态(无 Event 概念)
Bloc完整版,通过 Event → State 单向流管理
BlocBuilder监听状态变更并重建 UI
BlocProvider提供 Bloc/Cubit 实例
emit()发送新状态(Cubit/Bloc 核心方法)
基础案例(计数器,Cubit 版)
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// 1. 定义 Cubit 和状态
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0); // 初始状态

  // 状态修改方法
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

// 2. Bloc 页面
class BlocCounterPage extends StatelessWidget {
  const BlocCounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Bloc/Cubit 计数器")),
      body: Center(
        child: BlocBuilder<CounterCubit, int>( // 监听状态
          builder: (context, count) {
            return Text(
              "当前计数:$count",
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              // 获取 Cubit 并修改状态
              context.read<CounterCubit>().decrement();
            },
            child: Icon(Icons.remove),
          ),
          SizedBox(width: 10),
          FloatingActionButton(
            onPressed: () {
              context.read<CounterCubit>().increment();
            },
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

// App 入口需包裹 ProviderScope
void main() {
  runApp(MyApp());

}

class MyApp extends StatelessWidget{
  const MyApp({super.key});

  @override
  Widget build(BuildContext context){
    return BlocProvider(
      create: (context) => CounterCubit(),
      child: MaterialApp(
        home: BlocCounterPage()
      ),
    );
  }
}

注意事项
  1. Cubit 适合简单状态,Bloc 适合复杂业务(需区分 Event 和 State);
  2. 需在 dispose 时关闭 Cubit/Bloc(BlocProvider 会自动处理);
  3. 可通过 BlocListener 监听状态变更并执行副作用(如弹窗、路由跳转);
  4. 全局 Bloc 需在 App 根节点初始化(MultiBlocProvider)。

2.4 全局/局部状态划分

类型适用场景实现方式示例
全局状态全 App 共享,如用户信息、主题、语言MaterialApp 外层初始化状态管理器Provider/Riverpod/Bloc 包裹 MyApp
局部状态单页面/组件共享,如表单状态、弹窗状态在页面/组件层级初始化状态管理器仅在某个 Page 内包裹 Provider/Bloc
示例:全局状态(用户信息)
// 全局用户状态(Provider 版)
class UserNotifier extends ChangeNotifier {
  String _username = "未登录";
  String get username => _username;

  void login(String name) {
    _username = name;
    notifyListeners();
  }
}

// App 入口初始化全局状态
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => UserNotifier(), // 全局状态
      child: MaterialApp(
        home: ProviderCounterPage(),
      ),
    );
  }
}