Flutter开发者必备:状态管理Bloc的实用详解

本文首发于公众号:移动开发那些事:Flutter开发者必备:状态管理Bloc的实用详解

1 引言

Flutter应用开发中,状态管理是一个核心挑战。随着应用复杂度的提升,如何高效地管理状态、处理依赖注入以及实现路由导航,成为了开发者必须面对的问题。本文将探讨BLoc 框架的特点、基础使用以及高级应用场景,帮助开发者全面掌握这一框架。

2 BLoc介绍

BLoC(Business Logic Component)是一种由 Google 推出的状态管理模式,最初为 Angular 框架设计,后被广泛应用于 Flutter开发中。其核心思想是将业务逻辑与 UI 界面分离,通过流(Stream)实现单向数据流,使得状态变化可预测且易于测试。其中最关键的概念:

  • Event :用户交互或系统触发动作(如按钮点击、网络请求),以类或枚举形式定义;
  • State: 应用(业务)所处的状态(如加载中,加载错误,空数据等),每个状态对应于不同的UI展示逻辑
  • Bloc(逻辑组件): 接收业务的事件流,通过 mapEventToState或 on<Event>处理逻辑,输出新状态流;

它的数据流一般为:UI -> add(Event) (动作触发或某些条件触发) -> Bloc(处理对应事件,并转换出新的状态) -> emit(State) (提交新的状态机制) -> UI(接收到新状态,并重新沉浸)

并且这个框架的核心目标为:

  • 状态变更可预测:通过严格的 "事件 - 状态" 映射,确保状态变化可追溯、可复现;
  • 业务逻辑可测试:业务逻辑与 UI 解耦后,可通过单元测试验证,无需依赖 UI 环境;
  • 代码可维护性:分层设计使代码结构清晰,降低复杂度;
  • 响应式更新:基于数据流实现 UI 与状态的自动同步。

3 BLoc使用

下面以一个简单的计数器应用示例,展示 BLoC 模式的基本用法:

3.1 不使用框架实现BLoc

BLoC 模式的核心是基于DartStreamStreamController 实现的:

  • Stream :用于异步数据序列的核心抽象
  • StreamController : 是Stream 的控制器,可用于添加数据和监听事件;
  • Sink :用于向Stream 添加数据的入口;

这个模式的处理过程为:

  • UI 通过 Sink 将事件发送到 BLoC
  • BLoC 接收事件并处理业务逻辑
  • BLoC 将新状态添加到状态流
  • UI 通过 StreamBuilder 监听状态变化并更新
scala 复制代码
import 'dart:async';

// 步骤1: 定义事件类
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}

// 步骤2 . 定义状态类
class CounterState {
  final int count;
  CounterState(this.count);
}

// 步骤3. 实现 BLoC 逻辑
class CounterBloc {

  // 状态流
  final _stateController = StreamController<CounterState>();
  Stream<CounterState> get stateStream => _stateController.stream;
  
  // 事件流
  final _eventController = StreamController<CounterEvent>();
  Sink<CounterEvent> get eventSink => _eventController.sink;
  
  // 当前状态
  CounterState _state = CounterState(0);
  
  CounterBloc() {
    // 监听事件流并处理事件
    _eventController.stream.listen(_mapEventToState);
  }
  
  // 处理事件,并将新状态添加到状态流中
  void _mapEventToState(CounterEvent event) {
    if (event is IncrementEvent) {
      _state = CounterState(_state.count + 1);
    } else if (event is DecrementEvent) {
      _state = CounterState(_state.count - 1);
    }
    // 将新状态添加到状态流
    _stateController.add(_state);
  }
  
  void dispose() {
    _stateController.close();
    _eventController.close();
  }
}

// 步骤4 . 在 Flutter 中使用 BLoC
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('BLoC Counter')),
        body: CounterPage(),
      ),
    );
  }
}

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  final CounterBloc _counterBloc = CounterBloc();
  
  @override
  void dispose() {
    _counterBloc.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // 使用 StreamBuilder 监听状态的变化
          StreamBuilder<CounterState>(
            stream: _counterBloc.stateStream,
            initialData: _counterBloc._state,
            builder: (context, snapshot) {
            	// 这里可以根据不同的状态,返回不同的UI
              return Text(
                'Count: ${snapshot.data!.count}',
                style: TextStyle(fontSize: 24),
              );
            },
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(
                child: Text('+'),
                // 用户动作,触发事件生成,通过eventSink 将事件发送到BLoc
                onPressed: () => _counterBloc.eventSink.add(IncrementEvent()),
              ),
              ElevatedButton(
                child: Text('-'),
                // 用户动作,触发事件生成
                onPressed: () => _counterBloc.eventSink.add(DecrementEvent()),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

3.2 使用框架实现BLoc

在实际项目中,通常会使用官方的bloc 库或 flutter_bloc 库来简化 BLoC的实现, 使用前需要先在工程的pubspec.yaml文件中增加第三方库的依赖

yaml 复制代码
dependencies:
  flutter_bloc: ^9.1.1

使用flutter _bloc库实现同样的计数器的功能

scala 复制代码
// 使用 flutter_bloc 库实现相同功能
import 'package:flutter_bloc/flutter_bloc.dart';

// 步骤1:定义事件类
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}

// 步骤2:实现BLoC 类,这里通过继承Bloc来实现
// 这里没有直接定义CounterState类了,而是直接通过int来替代(实际使用中需要根据复杂程度来决定是否要状态类)
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);
  
  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
  	// 将事件转换为对应的状态
    if (event is Increment) {
    	// 通过yield发送新事件
      yield state + 1;
    } else if (event is Decrement) {
      yield state - 1;
    }
  }
}

// 步骤3:UI, 使用 BlocBuilder 简化 UI
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  	// 使用 BlocBuilder 简化 UI
    return BlocBuilder<CounterBloc, int>(
      builder: (context, count) {
      	// 这里可以根据不同的状态,返回不同的UI
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Count: $count', style: TextStyle(fontSize: 24)),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  child: Text('+'),
                  // 触发新事件的生成
                  onPressed: () => context.read<CounterBloc>().add(Increment()),
                ),
                ElevatedButton(
                  child: Text('-'),
                  onPressed: () => context.read<CounterBloc>().add(Decrement()),
                ),
              ],
            ),
          ],
        );
      },
    );
  }
}

这里只是实现了一个很简单的计数器的示例,实际的使用过程中需要业务和对应状态的复杂程度,灵活使用BlocListenerBlocBuilder,BlocProvider这几个组件来构建业务框架;

4 flutter_bloc 库

Flutter中,其实有两个库都能实现BLoc的模式:flutter_bloccubit,:

  • cubit: BLoc 模式的简化版,省略了事件类,直接通过emit去发射状态,相对轻量,比较适合简单状态管理或中小型的项目;
  • flutter_bloc : BLoc 模式的完整版,支持各种事件的监听,状态的转换,适配于复杂业务逻辑和大型项目; 有需要的可自行去了解cubit库的实现与使用,这里就只介绍flutter_bloc库。

4.1 核心类

  • Bloc:管理状态的核心类,处理事件并转换为状态;

  • Event:触发状态变化的抽象,通常为类;

  • State:应用状态的抽象,通常为类

  • BlocBuilder :响应状态变化的 Flutter 组件

  • BlocProvider :依赖注入工具(继承自 InheritedWidget),使 Bloc 可在 widget 树中访问;

  • BlocListener: 响应状态变化,适合处理一次性操作,如导航,弹窗等;

  • BlocSelector: 选择性重建 UI,避免不必要的刷新

  • BlocConsumer: 合并 BlocBuilderBlocListener

4.1.1 BlocBuilder

其的主要作用为:

  • 监听 Bloc 状态变化;
  • 状态变化时自动重建 widget
  • 接收 buildWhen 参数,用于控制是否重建
kotlin 复制代码
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Text('当前计数:${state.count}'); // 根据状态渲染UI
  },
);

4.1.2 BlocProvider

其的主要作用为:

  • 支持多层嵌套,形成 Bloc 层级结构;
  • 通过BlocProvider.of<BlocType>(context)让子 Widget 获取 Bloc
  • 负责 Bloc 的生命周期管理;
scss 复制代码
// 在Widget树顶层提供Bloc
BlocProvider(
  create: (context) => CounterBloc(), // 创建Bloc实例
  child: MaterialApp(home: CounterPage()),
);

// 在子Widget中获取Bloc
final counterBloc = BlocProvider.of<CounterBloc>(context);

4.1.3 BlocListener

核心特性:

  • 仅在状态变化时触发一次(不会因父组件重建而重复执行)
  • 不返回 Widget,仅用于执行副作用逻辑(异步操作)
  • 可以通过listenWhen控制是否响应特定状态变化
kotlin 复制代码
BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    // 当登录成功时导航到首页
    if (state is LoggedInState) {
      Navigator.pushReplacementNamed(context, '/home');
    }
    // 当登录失败时显示错误提示
    else if (state is LoginErrorState) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.errorMessage)),
      );
    }
  },
  // 仅在状态从非加载变为加载时响应
  listenWhen: (previous, current) {
    // 通过判断前一状态和当前状态,决定是否要响应这个状态的变化
    return previous is! LoadingState && current is LoadingState;
  },
  child: LoginForm(), // BlocListener返回的子组件
)

4.1.4 BlocSelector

Bloc 的状态包含大量数据,但 UI 只依赖其中一部分时,使用 BlocSelector 可以仅监听状态中的特定字段,避免不必要的 UI 重建,提高性能,它通过 selector回调从完整状态中提取所需数据,并通过equalityChecker判断数据是否变化(默认使用==比较)。只有当提取的数据确实变化时,才会重建 UI,关键特点为:

  • 从复杂状态中提取所需的部分数据
  • 只有当提取的数据发生变化时,才会触发重建
  • 减少不必要的 Widget 重建,优化性能
javascript 复制代码
// 假设个人中心的`UI` 只需显示用户名,使用 `BlocSelector` 可以避免在 isLoading 或 error 变化时重建
BlocSelector<UserBloc, UserState, String>(
  // 提取所需的状态部分
  selector: (state) => state.user.name,
  // 只有当用户名变化时才会重建
  builder: (context, userName) {
    return Text('用户名: $userName');
  },
)

4.1.5 BlocConsumer

BlocConsumer 是一个组合组件,它同时包含了BlocBuilderBlocListener 的功能,既可以处理副作用,又可以构建 UI。当需要在同一个Bloc 中同时处理这两种需求时,使用BlocConsumer 可以简化代码。几个核心特点为:

  • 整合了BlocBuilderBlocListener 的功能
  • 提供builder回调用于构建 UI
  • 提供listener回调用于处理副作用
  • 分别通过buildWhenlistenWhen控制重建和监听的条件
kotlin 复制代码
BlocConsumer<ProductBloc, ProductState>(
  // 处理副作用
  listener: (context, state) {
    if (state is ProductAddedToCartState) {
      // 展示一个提示信息
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('商品已加入购物车')),
      );
    }
  },
  // 构建UI
  builder: (context, state) {
    if (state is ProductLoadingState) {
      // 加载中
      return CircularProgressIndicator();
    } else if (state is ProductLoadedState) {
      // 正常商品
      return ProductDetailView(product: state.product);
    } else if (state is ProductErrorState) {
      // 错误状态
      return Text('加载失败: ${state.error}');
    }
    return Container();
  },
  // 控制何时重建UI
  buildWhen: (previous, current) {
    // 只有状态是加载、加载完成或错误时才重建
    return current is ProductLoadingState || 
           current is ProductLoadedState || 
           current is ProductErrorState;
  },
  // 控制何时触发listener
  listenWhen: (previous, current) {
    // 只有当状态变为ProductAddedToCartState时才触发
    return current is ProductAddedToCartState;
  },
)

4.2 Bloc类的原理

底层设计基于 Dart 的异步编程模型和 Stream 流,其核心实现原理可总结为:

  • 基于Dart Stream的响应式编程模型;
  • 通过事件驱动的状态机模式;
  • 利用InheritedWidget实现上下文传递
  • 严格分离业务逻辑与UI层

5 总结

本文围绕Flutter开发中的状态管理框架Bloc展开,详细介绍了其核心概念、使用方法、相关库及一些关键类,旨在帮助开发者了解该开发框架,并在实际开发中使用该框架来应对复杂应用开发中的状态管理挑战。

6 参考文档

相关推荐
bug_kada3 小时前
前端后端3步联调:Cookie认证实战,让登录功能完美上线!
前端·javascript
青晚舟3 小时前
作为前端你必须要会的CICD
前端·ci/cd
hj5914_前端新手3 小时前
深入分析 —— JavaScript 深拷贝
前端·javascript
中微子3 小时前
虚拟列表完全指南:从零到一手写实现
前端
jqq6663 小时前
解析ElementPlus打包源码(二、buildFullBundle)
前端·javascript·vue.js
YaeZed3 小时前
TypeScript6(class类)
前端·typescript
织_网3 小时前
UniApp 页面通讯方案全解析:从 API 到状态管理的最佳实践
前端·javascript·uni-app
emojiwoo4 小时前
前端视觉交互设计全解析:从悬停高亮到多维交互体系(含代码 + 图表)
前端·交互
xxy.c4 小时前
嵌入式解谜日志—多路I/O复用
linux·运维·c语言·开发语言·前端