前言:嘿,朋友!如果你想学习前端技术又觉得枯燥难懂,这个系列就是为你准备的------我们把 AI 当成一起成长的伙伴,用接地气的方式拆解每一项前端技能。常用提示词:"言简意赅的讲给文化程度不高的我"。
BLoC(Business Logic Component,业务逻辑组件) 让UI与业务逻辑分离变得更加容易,使你的代码更快, 易于测试, 并且易于复用。
BLoC 核心概念
本章节以"水管"这一比喻,讲解 Stream(数据水管)、Cubit(管工)与 BLoC(高级管工)这三个BLoC核心概念。
流(水管)
一句话概念: 流(Stream)="一条能源源不断送东西来的水管"
1. 把数据想成一桶桶水
- 数组 / 列表:像把几桶水一次性摆在你面前,你要就自己去拿。
- 流:像一根连着水厂的水管,水一桶桶顺序流过来,你不用跑,只要把杯子放在管口"接水"就行。
2. 为什么要用水管而不是整桶搬?
- 东西可能很多,分批送更省力:一首 3 小时的网络电台节目,如果一次全塞给手机,得等很久、占很多内存;换成"流",主播说一句你就听一句,边到边用。
- 不知道什么时候来,用流就能"等水" :比如传感器温度数据,啥时候变化你根本提前不知道,让它自己往水管里"滴",你只要接着就能实时知道温度。
- 出问题也容易止损:水管坏了,最多漏后面的水;一次性搬 100 桶,搬到 50 桶就发现漏了,那 50 桶前功尽弃。
3. 怎么用水管?
- 放水:写代码的人把数据"add"进去,就像往管子里放水。
- 接水 :你写
listen()
,就像把杯子(回调函数)放在管口,水来了就会自动倒进杯子。 - 关阀门 :如果不想喝了,可以
cancel()
,等于拧紧水阀,后面来的水你就不管了。
4. 和 BLoC 框架有啥关系?
在 BLoC 里:
- 事件流(Event Stream) :你把"按钮点击""滚动到底"等事件当水倒进去。
- 状态流(State Stream) :Bloc 里会加工、过滤,再把新的 UI 状态当水从另一根水管放出来。
- 你在 Widget 端只要接住"状态"那根水管,UI 就能跟着更新。你要做的事只有两件:把水倒进去/把杯子接上去。
5. "流"示例代码
python
Stream<int> countStream(int max) async* {
for (int i = 0; i < max; i++) {
yield i;
}
}
Future<int> sumStream(Stream<int> stream) async {
int sum = 0;
await for (int value in stream) {
sum += value;
}
return sum;
}
void main() async {
Stream<int> stream = countStream(10);
int sum = await sumStream(stream);
print(sum); // 45
}
Cubit(管工)
Cubit = 一个专门负责"记状态、改状态、广播状态"的小工头。
你把它想成管工,专门照管那根"状态水管"(流)。
1. 回顾一下"水管"故事
- 流(Stream) 就像一根水管,水(数据/状态)一口口流出来。
- 你 拿着茶杯(监听函数)接水,水来了就能喝到最新状态。
2. 可问题来了------水谁来倒?
如果每次都要自己 yield
或 add
往管子里倒水,
- 逻辑杂七杂八,
- 容易忘记关阀门,
- 代码到处都是"倒水"细节。
这时候就需要派 Cubit 出场:
"交给我!倒水 + 记账 + 广播,我全包!"
3. Cubit 的三件法宝
法宝 | 通俗解释 | 关键点 |
---|---|---|
state |
水桶:当前存着的状态 | 任何时候想知道"现在啥情况",直接看它 |
emit(newState) |
倒水:把最新状态倒进水管 | 一调用,所有接水的人都同时喝到新水 |
close() |
关阀门、收工 | 用完 Cubit 要关,不然水管还在占资源 |
4. Cubit 的基本用法
scss
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
}
void main() {
final cubit = CounterCubit();
print(cubit.state); // 0
cubit.increment();
print(cubit.state); // 1
cubit.close();
}
BLoC(高级管工)
BLoC 就是一名 高级管工------先收集指令,再调度工人和材料,最后把处理好的"状态水"统一放进水管,界面只管接水更新,不必关心后台怎么忙碌。
1 先回顾两位同事
BLoC(Business Logic Component,业务逻辑组件) 让UI与业务逻辑分离变得更加容易,使你的代码 更快, 易于测试, 并且 易于复用。
1 先回顾两位同事
角色 | 职责 |
---|---|
Stream(水管) | 源源不断送水(数据),杯子(监听函数)一接就喝到最新内容。 |
Cubit(小管工) | 手拿一个水桶 state,收到命令立刻往水管倒新水(emit),动作简单直接。 |
2 高级管工 BLoC 与小管工 Cubit 的区别
- Cubit: "老板说刷墙白,我立即刷,刷完马上告诉你。" → 适合一步到位的小活儿。
- BLoC: "老板发来'翻新客厅'指令,我先登记事件 → 查油漆库存 → 排班 → 协调脚手架 → 完工后一次性汇报新状态。" → 适合流程多、环节多的复杂活。
3 为什么需要这位高级管工?
价值 | 说明 |
---|---|
彻底分离 UI 与业务 | UI 只监听状态水管;所有 if/else、网络、缓存逻辑都由 BLoC 内部处理 |
统一处理副作用 | 网络请求、本地存储等都集中在 on<Event> / mapEventToState 中,方便维护 |
测试友好 | 给一串事件,预期输出一串状态,单元测试变简单、稳定 |
逻辑可复用 | 一个 BLoC 可以被多个页面共享(如登录状态、购物车),无需复制粘贴 |
4 整条流水线
指令 Event → 高级管工 BLoC → emit 新 State → Stream 水管 → UI 接水刷新
- 收指令 :
add(Event)
把需求交给管工 - 调度作业 :BLoC 内部
on<Event>
里查料、排队、加工 - 倒状态水 :
emit(State)
一次性把结果送上水管 - 界面喝水 :
BlocBuilder/Listener
自动刷新界面或触发副作用
Flutter BLoC
Flutter BLoC极简Demo
要跑起来 BLoC 其实只需要三样:Bloc(逻辑) → BlocProvider(把逻辑送到界面) → BlocBuilder(把界面跟状态绑在一起) ,下面是一个例子:打开一个页面,显示一个数字;按浮动按钮,数字 +1。
1. 添加依赖
在 pubspec.yaml
里加入(最新版号可用 flutter pub add flutter_bloc
自动写):
yaml
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.5
2. 代码(lib/main.dart)
scala
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
/// ① 定义事件(只有一个增量事件)
abstract class CounterEvent {}
class CounterIncremented extends CounterEvent {}
/// ② Bloc:接收事件 → 输出状态
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncremented>((event, emit) => emit(state + 1));
}
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (_) => CounterBloc(), // 提供 Bloc
child: const CounterPage(),
),
);
}
}
/// ③ BlocBuilder 监听 Bloc 的状态
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
final bloc = context.read<CounterBloc>();
return Scaffold(
appBar: AppBar(title: const Text('BLoC 极简 Demo')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (_, count) => Text(
'$count',
style: const TextStyle(fontSize: 52),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => bloc.add(CounterIncremented()), // 发送事件
child: const Icon(Icons.add),
),
);
}
}
3. 运行
arduino
flutter pub get
flutter run
Flutter BLoC核心概念
名字 | 作用 | 属于哪一类 |
---|---|---|
BlocProvider | 把一个 Bloc 实例放进树里,下面的界面都能拿到 | 核心 |
BlocBuilder | 监听状态,重建 UI | 核心 |
BlocListener | 只做一次性动作(弹 Toast / 跳页面),不重建 UI | 常用,但可用 BlocConsumer 代替 |
BlocConsumer | Builder + Listener 二合一,图省事 | 辅助 |
BlocSelector | 只听状态里一小块,减少不必要重建 | 优化 |
MultiBlocProvider / MultiBlocListener | 一次提供/监听好几个 Bloc,用语法糖少写嵌套 | 辅助 |
RepositoryProvider / MultiRepositoryProvider | 注入"仓库"或网络 DAO,Bloc 里可以拿来用 | 辅助 |
context.read() | 立即拿 Bloc/Repo,不会重建 | 工具方法 |
context.watch() | 拿 Bloc/Repo,每次它变都会重建 | 工具方法 |
context.select<T,S>() | 只监听某个字段 | 优化型工具 |