不管在什么样的应用中,如果要处理大量的数据,不可避免的就是要定义大量的数据类用来装载和解析数据,在 Flutter 中也不例外,今天要介绍的这个 Freezed 库就是 Flutter 中用来作为数据类(data classes)代码生成的这样一款工具。
freezed 是什么
freezed 是一个 Flutter/Dart 生态系统中一个非常强大的代码生成工具,用于创建数据类,基于 Dart 的代码生成功能,通过自动生成 data classes, tagged unions, nested classes 和 clone 代码模板,大大减少了手动编写重复性代码的工作量。freezed 的设计非常类似 Java 生态中的 [[Lombok]],通过注解和代码生成来减少样板代码量。
尽管 Dart 很棒,但是在定义 Model 的时候还是非常乏味,编程者需要
- 定义构造函数,属性
- 重载 toString, == hashCode 等
- 实现 copyWith 方法来 clone 对象
- 处理序列化以及反序列化等
如果要实现这一些,一个 Model 可能就需要上百行代码,这不仅容易出错,而且还影响了代码易读性。freezed 就是设计用来来帮助开发者只需要关注定义 Model,而无需考虑其他。
比如一个简单的用户定义
class Person with _$Person {
Person({
required this.firstName;
required this.lastName;
required this.age;
})
final String firstName;
final String lastName;
final String age;
}
freezed 安装
为了使用 Freezed,需要依赖 build_runner 代码生成,首先安装 build_runner 和 Freezed,添加依赖到 pubspec.yaml
中。
通过命令行添加
flutter pub add freezed_annotation
flutter pub add dev:build_runner
flutter pub add dev:freezed
# if using freezed to generate fromJson/toJson, also add:
flutter pub add json_annotation
flutter pub add dev:json_serializable
直接修改 pubspec.yaml 文件
dependencies:
flutter:
sdk: flutter
freezed_annotation: ^3.0.4
json_annotation: ^4.0.6 # 如需JSON序列化支持
dev_dependencies:
build_runner: ^2.3.2
freezed: ^3.0.4
json_serializable: ^6.5.4 # 如需JSON序列化支持
然后运行 flutter pub get
说明
- build_runner 用来运行代码生成
- freezed 是代码生成器
- freezed_annotation,包含了 freezed 注解
运行代码生成
dart run build_runner watch -d
# or
flutter pub run build_runner build --delete-conflicting-outputs
和其他代码生成器需要的一样,freezed 需要导入 freezed 注解,并且需要使用 part 关键字
import 'package:freezed_annotation/freezed_annotation.dart';
part 'my_file.freezed.dart';
freezed 使用
为了避免与 JSON 序列化相关的警告,建议在项目根目录的analysis_options.yaml
文件中添加以下配置
analyzer:
errors:
invalid_annotation_target: ignore
创建 Model
Freezed 提供两种方式来创建 data-classes
- Primary constructors,定义构造函数,Freezed 生成关联字段
- Classic classes,编写正常的 Dart 类,Freezed 只生成 toString/ == / copyWith
Primary constructors
Freezed 实现 Primary Constructors 通过 factory 关键字。
import 'package:freezed_annotation/freezed_annotation.dart';
// required: associates our `main.dart` with the code generated by Freezed
part 'main.freezed.dart';
// optional: Since our Person class is serializable, we must add this line.
// But if Person was not serializable, we could skip it.
part 'main.g.dart';
@freezed
abstract class Person with _$Person {
const factory Person({
required String firstName,
required String lastName,
required int age,
}) = _Person;
factory Person.fromJson(Map<String, Object?> json) => _$PersonFromJson(json);
}
定义了 Person
- 属性
- 使用了
@freezed
注解,类属性是不可变的 - 定义了 fromJson,序列化和反序列化,Freezed 会添加 toJson 方法
- Freezed 会自动生成 toString / == / hashCode / copyWith
执行命令
flutter pub run build_runner build --delete-conflicting-outputs
命令将生成 xxx.freezed.dart
文件,以及 xxx.g.dart
包含序列化相关的代码
如果有一些情况需要定义 getters 或者自定义方法,这个时候需要定义一个空的构造函数
@freezed
abstract class Person with _$Person {
// Added constructor. Must not have any parameter
const Person._();
const factory Person(String name, {int? age}) = _Person;
void method() {
print('hello world');
}
}
定义可变类
需要使用 @unfreezed
@unfreezed
abstract class Person with _$Person {
factory Person({
required String firstName,
required String lastName,
required final int age,
}) = _Person;
factory Person.fromJson(Map<String, Object?> json) => _$PersonFromJson(json);
}
姓名是可变的
void main() {
var person = Person(firstName: 'John', lastName: 'Smith', age: 42);
person.firstName = 'Mona';
person.lastName = 'Lisa';
}
并且不会实现 == / hashCode 方法,所以下面的比较等于 false
void main() {
var john = Person(firstName: 'John', lastName: 'Smith', age: 42);
var john2 = Person(firstName: 'John', lastName: 'Smith', age: 42);
print(john == john2); // false
}
让 Lists/Maps/Sets 可变
通常情况下,如果使用 @freezed
,那么内部属性如果使用 List/Map/Set 会自动变成不可变
@freezed
abstract class Example with _$Example {
factory Example(List<int> list) = _Example;
}
void main() {
var example = Example([]);
example.list.add(42); // throws because we are mutating a collection
}
需要调整为
@Freezed(makeCollectionsUnmodifiable: false)
abstract class Example with _$Example {
factory Example(List<int> list) = _Example;
}
void main() {
var example = Example([]);
example.list.add(42); // OK
}
Classic classes
和之前提到的 Primary constructors 相比,这个模式下,我们只需要定义普通的 Dart classes。
通常编写 constructor 和 fields 定义。
import 'package:freezed_annotation/freezed_annotation.dart';
// required: associates our `main.dart` with the code generated by Freezed
part 'main.freezed.dart';
// optional: Since our Person class is serializable, we must add this line.
// But if Person was not serializable, we could skip it.
part 'main.g.dart';
@freezed
@JsonSerializable()
class Person with _$Person {
const Person({
required this.firstName,
required this.lastName,
required this.age,
});
final String firstName;
final String lastName;
final int age;
factory Person.fromJson(Map<String, Object?> json)
=> _$PersonFromJson(json);
Map<String, Object?> toJson() => _$PersonToJson(this);
}
Freezed 会自动处理 copyWith / toString / == / hashCode 。
related
- DartJ 是一个可以将 JSON 快速转变成 Dart 类定义的工具。