- 前端
【免费下载链接】bloc
A predictable state management library that helps implement the BLoC design pattern
HydratedCubit 是 bloc 状态管理库提供的「可持久化 Cubit」形态:它继承了HydratedCubit,通过toJson/fromJson将状态自动持久化到本地存储,App 重启后无需手动恢复状态。本指南以仓库中bricks/hydrated_cubit官方 Brick(Mason 模板)为核心,讲解它的演进历史、模板结构、生成方式与三种代码风格(basic / equatable / freezed),并深入其源码细节,帮助你快速生成、定制并正确落地可持久化的 Cubit 代码。
一、Brick 是什么:hydrated_cubit的定位
bricks/hydrated_cubit是 bloc 仓库中官方维护的一组 Mason Brick,定位是「Generate a new HydratedCubit in Dart. Built for the bloc state management library」(见 README.md)。它服务于以下两类典型场景:
- 已有基于
hydrated_bloc的 Dart / Flutter 项目,需要快速创建带本地持久化能力的 Cubit 骨架; - 团队需要统一 Cubit 代码风格,避免每个人手写结构不一致。
与普通cubitBrick 的关键区别在于:生成的类继承自HydratedCubit<T>而非Cubit<T>,因此模板强制要求实现toJson与fromJson两个方法,这也是持久化能力所在。与之配套的还有hydrated_bloc(完整 Bloc)与replay_cubit/replay_bloc等兄弟 Brick,共同组成 bloc 生态的模板体系。
二、版本演进:从 0.1.0 到 0.3.0
Brick 的版本历史完整记录在 CHANGELOG.md 中,其演进脉络清晰反映了模板能力的扩展过程:
| 版本 | 变更类型 | 内容 |
|---|---|---|
| 0.1.0 | feat | 初始发布,支持 basic 风格的 hydrated cubit 生成 |
| 0.1.1 | docs | 对 README 做小幅更新 |
| 0.1.2 | docs | README 增加徽章(badges),并使用深色 Logo 变体 |
| 0.1.3 | fix | 修复 part 指令与 import 的声明问题 |
| 0.2.0 | feat | 新增equatable与freezed两种风格的模板支持 |
| 0.2.1 | chore | 更新版权年份与 Logo 图片引用 |
| 0.3.0 | chore | 升级依赖:mason ^0.1.0,hooks 升级至dart ^3.5.4 |
可以提炼出三条事实:
- 模板能力分层演进:0.1.0 仅支持 basic 风格,0.2.0 才引入 equatable 与 freezed,因此
style变量(见下文)的三个取值并非同时出现; - 0.1.3 的 part/imports 修复对应模板文件中
part '{{name.snakeCase()}}_state.dart';与part of的配对关系,这正是多文件生成 Brick 最容易出错的地方; - 0.3.0 对齐了工具链版本:Brick 依赖
mason ^0.1.0,hooks 运行环境要求dart ^3.5.4,使用时需确保本地 mason CLI 与 Dart SDK 满足该约束(可核对 brick.yaml 与 hooks/pubspec.yaml)。
三、使用方式:一条命令生成两个文件
按 README.md 的说明,使用方式极其简单:
mason make hydrated_cubit --name counter --style basic执行后会在当前目录下生成:
├── counter_cubit.dart └── counter_state.dart使用前提:本地已安装 Mason CLI)。生成的counter_cubit.dart与counter_state.dart需要放置在你的lib/目录中,并确保pubspec.yaml已引入hydrated_bloc(freezed 风格还需freezed_annotation与 build_runner 配合)。
四、变量与参数:name 与 style 详解
Brick 的输入变量定义在 brick.yaml 中,共两个:
| 变量 | 类型 | 默认值 | 可选值 | 说明 |
|---|---|---|---|---|
name | string | counter | 任意字符串 | Cubit 类名,命令行交互提示 "Please enter the cubit name." |
style | enum | basic | basic、equatable、freezed | 生成模板风格,交互提示 "What is the cubit style?" |
命令行交互方式(不传参时按提示输入):
mason make hydrated_cubit # ? Please enter the cubit name. counter # ? What is the cubit style? basicname在模板中会被 Mason 的变量修饰器进一步处理:{{name.snakeCase()}}用于文件名与 part 指令(如counter_cubit.dart),{{name.pascalCase()}}用于类名(如CounterCubit/CounterState)。
style的分流逻辑并不写在模板的 if 条件里,而是由 hooks/pre_gen.dart 在生成前将枚举值转换为三个布尔变量:
final style = context.vars['style']; context.vars = { ...context.vars, 'use_basic': style == 'basic', 'use_equatable': style == 'equatable', 'use_freezed': style == 'freezed', };随后模板主文件 {{name.snakeCase()}}_cubit.dart%7D%7D_cubit.dart) 通过 Mustache 区块按需引入对应片段:
{{#use_freezed}}{{> freezed_cubit }}{{/use_freezed}}{{#use_equatable}}{{> equatable_cubit }}{{/use_equatable}}{{#use_basic}}{{> basic_cubit }}{{/use_basic}}{{name.snakeCase()}}_state.dart%7D%7D_state.dart) 采用相同的三段式分发。这种「pre_gen 预计算变量 + 主文件按片段拼接」的模式,是 Mason 多风格 Brick 的通用最佳实践,便于后续新增风格时只需添加片段文件与 hook 分支。
五、三种生成风格与底层实现剖析
5.1 basic:最小可持久化 Cubit
basic 风格的 Cubit 模板见 {{~ basic_cubit }}:
import 'package:hydrated_bloc/hydrated_bloc.dart'; part 'counter_state.dart'; class CounterCubit extends HydratedCubit<CounterState> { CounterCubit() : super(const CounterState()); @override Map<String, dynamic> toJson(CounterState state) { // TODO: implement toJson } @override CounterState fromJson(Map<String, dynamic> json) { // TODO: implement fromJson } }对应状态模板见 {{~ basic_state }}:
part of 'counter_cubit.dart'; class CounterState { const CounterState(); }两个要点:
- 继承自
HydratedCubit<T>:这是与普通 Cubit 模板的核心差异。HydratedCubit位于仓库的 packages/hydrated_bloc 包中,它在每次emit新状态后自动调用toJson序列化并写入存储,在 App 启动恢复时调用fromJson反序列化,从而在原生状态管理之上叠加了透明的本地持久化能力; - 模板刻意留白:
toJson/fromJson以// TODO: implement占位,交由开发者按自己的状态模型填充,因为 Brick 无法预知业务状态的字段结构。
5.2 equatable:带值比较的持久化状态
equatable 风格的 Cubit 与 basic 几乎一致(仅 import 增加equatable),关键差异在状态模板 {{~ equatable_state }}:
part of 'counter_cubit.dart'; class CounterState extends Equatable { const CounterState(); @override List<Object> get props => []; }props目前为空列表,生成后需按业务字段补充,例如:
class CounterState extends Equatable { const CounterState({this.count = 0}); final int count; @override List<Object> get props => [count]; }Equatable的价值在于:当状态值未变化时,==与hashCode判定相等,bloc 生态可据此跳过冗余 rebuild,从而减少不必要的 Widget 重建与状态派发。
5.3 freezed:代码生成与不可变状态
freezed 风格引入 Dart 代码生成,Cubit 模板见 {{~ freezed_cubit }}:
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; part 'counter_state.dart'; part 'counter_cubit.freezed.dart'; class CounterCubit extends HydratedCubit<CounterState> { CounterCubit() : super(const CounterState.initial()); @override Map<String, dynamic> toJson(CounterState state) { // TODO: implement toJson } @override CounterState fromJson(Map<String, dynamic> json) { // TODO: implement fromJson } }状态模板见 {{~ freezed_state }}:
part of 'counter_cubit.dart'; @freezed class CounterState with _$CounterState { const factory CounterState.initial() = _Initial; }两个值得注意的细节:
- 多了一个
part '{{name.snakeCase()}}_cubit.freezed.dart';:这是 freezed 代码生成产生的文件,必须在运行build_runner后才会出现,因此在 CI/团队协作中需先执行生成命令再编译; - 初始状态由命名构造
CounterState.initial()提供:freezed 的 union/sealed 风格让后续扩展多个状态分支(如loading、error、loaded)变得非常自然,例如:
@freezed class CounterState with _$CounterState { const factory CounterState.initial() = _Initial; const factory CounterState.value(int count) = _Value; }三种风格的选择建议:追求最小依赖选 basic;需要频繁比较状态是否变化(配合BlocBuilder/BlocSelector优化重建)选 equatable;需要不可变、可模式匹配的多分支状态且团队已接受 build_runner 工作流选 freezed。此建议由 bricks/cubit(同样支持三种风格)及 packages/flutter_bloc 的公开设计推断得出。
六、与 bloc 仓库生态的联动
hydrated_cubitBrick 在整个 bloc 仓库中并非孤立的模板,理解其上下文有助于正确使用:
hydrated_bloc包(packages/hydrated_bloc)提供HydratedCubit基类与存储抽象,是生成的代码能运行的运行时前提;其示例(example)与测试(test)展示了持久化行为如何被验证;- 兄弟 Brick:
bricks/cubit(无持久化的纯 Cubit)、bricks/hydrated_bloc(持久化 Bloc)、bricks/replay_cubit/bricks/replay_bloc(可回放状态流)、bricks/flutter_bloc_feature(面向 Flutter 的完整 feature 脚手架)。它们共享name+style的变量设计,学习hydrated_cubit后可以零成本迁移到其他 Brick; - hooks 机制:
pre_gen.dart是 Mason 的生成前钩子,pubspec.yaml(hooks/pubspec.yaml)声明了 hooks 自身的依赖与 SDK 约束,这与 0.3.0 中「升级 hooks 到 dart ^3.5.4」的变更相互印证。
七、常见问题与最佳实践
为什么
toJson返回Map<String, dynamic>而不是直接存对象?因为HydratedCubit的存储层(默认基于本地文件/存储抽象)以 JSON 可序列化的 Map 为持久化单元。返回 null 时表示该状态不需持久化(例如某些瞬时状态可以跳过存储)。part与part of必须配对:Cubit 文件中part 'xxx_state.dart';,状态文件中part of 'xxx_cubit.dart';。文件名由snakeCase()统一派生,这正是 0.1.3 版本修复的重点,改动文件名时务必保持两者一致。freezed 风格编译报错找不到
freezed.dart:需要先运行代码生成:dart run build_runner build建议将其纳入 CI 流程。
持久化不生效的排查路径:确认
HydratedBloc.storage已在main()中初始化(参考hydrated_bloc包文档);确认状态类字段都已纳入toJson/fromJson;确认使用了HydratedCubit而不是普通Cubit。多文件生成的命名一致性:所有模板文件都基于同一个
name变量,通过snakeCase()/pascalCase()派生文件名与类名,因此只要name取规范的小驼峰/小写下划线形式,生成的文件与类即可保证互相匹配。
八、小结
bricks/hydrated_cubit是一个「小而精」的官方 Brick:两条变量(name、style)、两个输出文件、三种代码风格,再叠加HydratedCubit的持久化语义与pre_genhook 的分流逻辑,构成了 bloc 仓库中可持久化 Cubit 的标准生成入口。从 CHANGELOG.md 的演进可以看到它的成熟路径,而从 brick.yaml 与brick模板则能完整复现它的工作机制。若你想进一步定制(如增加新的风格、补充默认的 JSON 序列化实现),以本 Brick 为起点修改是成本最低的路径。
- 前端
【免费下载链接】bloc
A predictable state management library that helps implement the BLoC design pattern
相关推荐
霞鹜文楷:免费商用楷体中文字体完整指南,3 分钟装好
霞鹜文楷:免费商用楷体中文字体完整指南,3 分钟装好 霞鹜文楷是一款基于 FONTWORKS Klee One 衍生的开源楷体中文字体,覆盖简繁日汉字 2 万余
前端Cult Directory Template认证配置避坑指南:邮件确认与SMTP速率限制详解
Cult Directory Template认证配置避坑指南:邮件确认与SMTP速率限制详解 Cult Directory Template 是一款基于 Ne
如何用city-roads一键生成城市道路艺术地图:完整可视化指南
如何用city roads一键生成城市道路艺术地图:完整可视化指南 你是否曾想过将城市的脉络以艺术化的方式呈现?传统的城市道路可视化工具往往复杂难用,而city
前端数据可视化3D渲染
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考