用途
vgv-bloc 是 verygoodopensource/vgv-ai-flutter-plugin 提供的一项技能,主题为 Flutter/Dart 中 Bloc 状态管理的最佳实践。Bloc 是 Dart 与 Flutter 的状态管理库,采用 BLoC(Business Logic Component)模式,将业务逻辑与展示层分离。该技能用于在编写 Bloc/Cubit 相关代码时遵循统一的标准与约定。
核心标准
以下标准适用于所有 Bloc/Cubit 工作:
- 所有 Bloc 与 Cubit 测试使用 package:bloc_test 的 blocTest(),不要使用原始 test() 加手动流断言。
- 使用 package:mocktail 进行 mock,不要使用 package:mockito。
- 不允许 bloc 与 bloc 之间直接依赖,bloc 之间通过 UI 或共享 repository 通信。
- Page/View 分离:Page 通过 BlocProvider 提供 Bloc/Cubit,View 通过 BlocBuilder/BlocListener 消费。
- 事件与多状态类型使用 sealed class,以便配合 Dart 3 switch 进行穷尽模式匹配。
- 所有状态与事件使用 Equatable,继承 Equatable 并重写 props 以实现值相等。
- 业务逻辑只放在 Bloc/Cubit 中,不要放在 widget、page 或 view 中。
- 单一职责:每个功能关注点对应一个 Bloc/Cubit。
- 仅在异步检查之后 emit:只在 handler 回调内部使用 emit。
Cubit 与 Bloc 的对比
| 方面 | Cubit | Bloc |
|---|---|---|
| API | 函数 → emit(state) | 事件 → on<Event> → emit(state) |
| 复杂度 | 低 | 较高 |
| 可追踪性 | 较少(无事件日志) | 完整(事件 + 状态转换) |
| 适用场景 | 简单状态、UI 驱动逻辑 | 复杂流程、事件驱动、需要转换 |
| 测试 | 调用方法,断言状态 | 添加事件,断言状态 |
Cubit 示例
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
Bloc 示例
sealed class CounterEvent extends Equatable {
const CounterEvent();
@override
List<Object> get props => [];
}
final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
on<CounterDecrementPressed>((event, emit) => emit(state - 1));
}
}
命名约定
事件
模式:BlocSubject + Noun + VerbPastTense。
| 事件类名 | 含义 |
|---|---|
| TodoListSubscriptionRequested | 订阅 todo 列表流 |
| TodoListTodoDeleted | 删除特定 todo |
| TodoListUndoDeletionRequested | 撤销上一次删除 |
| LoginFormSubmitted | 提交登录表单 |
| ProfilePageRefreshed | 刷新个人资料页 |
sealed class TodoListEvent extends Equatable {
const TodoListEvent();
@override
List<Object> get props => [];
}
final class TodoListSubscriptionRequested extends TodoListEvent {}
final class TodoListTodoDeleted extends TodoListEvent {
const TodoListTodoDeleted({required this.todo});
final Todo todo;
@override
List<Object> get props => [todo];
}
状态
子类方式(多种状态类型)
当每种状态携带不同数据时使用。
| 状态类名 | 含义 |
|---|---|
| LoginInitial | 尚未执行任何操作 |
| LoginInProgress | 登录请求进行中 |
| LoginSuccess | 登录成功 |
| LoginFailure | 登录失败 |
sealed class LoginState extends Equatable {
const LoginState();
@override
List<Object> get props => [];
}
final class LoginInitial extends LoginState {}
final class LoginInProgress extends LoginState {}
final class LoginSuccess extends LoginState {
const LoginSuccess({required this.user});
final User user;
@override
List<Object> get props => [user];
}
final class LoginFailure extends LoginState {
const LoginFailure({required this.error});
final String error;
@override
List<Object> get props => [error];
}
单类方式(一个状态,多个字段)
当所有状态共享相同数据形状时使用。
| 字段 | 类型 | 用途 |
|---|---|---|
| status | enum | 当前加载状态 |
| items | List<Item> | 已加载数据 |
| error | String? | 失败时的错误信息 |
enum TodoListStatus { initial, loading, success, failure }
class TodoListState extends Equatable {
const TodoListState({
this.status = TodoListStatus.initial,
this.todos = const [],
this.error,
});
final TodoListStatus status;
final List<Todo> todos;
final String? error;
TodoListState copyWith({
TodoListStatus? status,
List<Todo>? todos,
String? error,
}) {
return TodoListState(
status: status ?? this.status,
todos: todos ?? this.todos,
error: error ?? this.error,
);
}
@override
List<Object?> get props => [status, todos, error];
}
架构
| 层 | 包含 | 依赖 |
|---|---|---|
| Presentation | Pages、Views、Widgets | Business Logic |
| Business Logic | Blocs、Cubits | Data |
| Data | Repositories、Data Providers | 外部来源 |
数据层
Repository 抽象数据源,为 Bloc/Cubit 提供干净的 API。所有测试文件在 test/ 下镜像功能文件夹结构。
关于 repository 示例、功能文件夹结构与测试目录布局,参见 references/architecture.md。
Flutter Widgets
- BlocProvider — 创建并向子树提供 Bloc/Cubit
- BlocBuilder — 状态变化时重建 widget
- BlocListener — 状态变化时执行副作用(导航、snackbar)
- BlocConsumer — 组合 BlocBuilder + BlocListener
- BlocSelector — 仅在选定属性变化时重建
在回调(onPressed、onTap)中使用 context.read,在 build 方法中使用 context.watch 或 BlocBuilder。不要在 build 之外使用 context.watch。
关于完整的 widget 与 context 扩展表、Page/View 模式示例与 BlocListener 示例,参见 references/widgets.md。关于 blocTest() 参数、Cubit/Bloc 测试示例、mock 依赖与 widget 集成测试,参见 references/testing.md。关于使用 Bloc/Cubit 添加功能、异步操作与事件转换器,参见 references/patterns.md。
适用场景与限制
该技能适用于使用 Bloc/Cubit 进行状态管理的 Flutter/Dart 项目,尤其是需要统一事件与状态命名、分层架构和测试方式的团队。资料未提供具体的安装命令、版本要求、收费信息或平台限制。