Flutter代码生成插件sealed_generators的使用
Flutter代码生成插件sealed_generators的使用
Dart Sealed Class Generator
生成Dart和Flutter的密封类层次结构。
特性
- 生成具有抽象超类型和数据子类的密封类。
- 静态工厂方法。例如
Result.success(data: 0)
。 - 类型转换方法。例如
a.asSuccess
,a.isSuccess
或a.asSuccessOrNull
。 - 三种类型的相等性和hashCode生成:数据(类似于Kotlin数据类)、身份和唯一。
- 使用流行的Equatable库实现数据相等性。
- 支持泛型。即使类型可以混合。
- 支持在空安全项目中的可空和不可空类型。
- 支持在一个密封类型中使用另一个密封类型。
- 支持空安全。
- 为数据类生成toString方法。
- 生成六种不同匹配方法。例如
when
,maybeWhen
和map
。
使用
在您的 pubspec.yaml
文件中添加依赖项。
dependencies:
sealed_annotations: ^latest.version
dev_dependencies:
sealed_generators: ^latest.version
导入 sealed_annotations
。
import 'package:sealed_annotations/sealed_annotations.dart';
添加 part
指向一个文件,您希望在此文件中生成类。扩展名为 .sealed.dart
。
part 'weather.sealed.dart';
添加 @Sealed
注解,并将一个抽象私有类作为生成代码的清单。例如:
@Sealed()
abstract class _Weather {
void sunny();
void rainy(int rain);
void windy(double velocity, double? angle);
}
然后运行以下命令以生成代码。如果您是Flutter开发者:
flutter pub run build_runner build
如果您正在开发纯Dart项目:
dart run build_runner build
生成的代码如下所示(以下代码已总结):
abstract class Weather {
const factory Weather.rainy({required int rain}) = WeatherRainy;
bool get isRainy => this is WeatherRainy;
WeatherRainy get asRainy => this as WeatherRainy;
WeatherRainy? get asRainyOrNull {
/* ... */
}
/* ... */
R when<R extends Object?>({
required R Function() sunny,
required R Function(int rain) rainy,
required R Function(double velocity, double? angle) windy,
}) {
/* ... */
}
R maybeWhen<R extends Object?>({
R Function()? sunny,
R Function(int rain)? rainy,
R Function(double velocity, double? angle)? windy,
required R Function(Weather weather) orElse,
}) {
/* ... */
}
R? whenOrNull<R extends Object?>({
R Function()? sunny,
R Function(int rain)? rainy,
R Function(double velocity, double? angle)? windy,
R Function(Weather weather)? orElse,
}) {
/* ... */
}
R map<R extends Object?>({
required R Function(WeatherSunny sunny) sunny,
required R Function(WeatherRainy rainy) rainy,
required R Function(WeatherWindy windy) windy,
}) {
/* ... */
}
R maybeMap<R extends Object?>({
R Function(WeatherSunny sunny)? sunny,
R Function(WeatherRainy rainy)? rainy,
R Function(WeatherWindy windy)? windy,
required R Function(Weather weather) orElse,
}) {
/* ... */
}
R? mapOrNull<R extends Object?>({
R Function(WeatherSunny sunny)? sunny,
R Function(WeatherRainy rainy)? rainy,
R Function(WeatherWindy windy)? windy,
R Function(Weather weather)? orElse,
}) {
/* ... */
}
}
class WeatherSunny extends Weather {
/* ... */
}
class WeatherRainy extends Weather with EquatableMixin {
WeatherRainy({required this.rain});
final int rain;
@override
String toString() => 'Weather.rainy(rain: $rain)';
@override
List<Object?> get props => [rain];
}
class WeatherWindy extends Weather {
/* ... */
}
注意事项:
- 尽量使用超类中的工厂方法而不是子类构造函数。例如
Whether.rainy()
而不是WhetherRainy()
。 - 最小化使用类型转换方法,大多数情况下它们可以用匹配方法替换。
相等性和生成的类名
您可以选择三种类型的相等性,通过 @WithEquality(...)
注解指定。默认相等性是 data
如果未指定。这将成为所有子类的默认相等性。您可以使用此注解在单个方法上更改每个子类的相等性。
相等性类型:
data
相等性使用Equatable包实现。它类似于Kotlin的数据类。identity
只有相同实例才是相等的。这就像你没有实现任何特定的相等性一样。distinct
所有实例都不相等。即使是实例本身也不相等。
基本示例:
@Sealed()
abstract class _Weather {
void sunny();
void rainy(int rain);
void windy(double velocity, double? angle);
}
在这个例子中,所有类都将具有 data
相等性。如果你想要所有类的 identity
相等性,但 windy
的 distinct
相等性:
@Sealed()
@WithEquality(Equality.identity)
abstract class _Weather {
void sunny();
void rainy(int rain);
@WithEquality(Equality.distinct)
void windy(double velocity, double? angle);
}
一个抽象超类会生成,其名称等于清单类的名称(这里为 Weather
)。每个方法都会成为一个子类。应该至少有一个方法。子类名称基于方法名称前缀(例如 WeatherSunny
)。命名过程可以通过使用 @WithPrefix
和 @WithName
注解来定制。每个方法参数会成为相应子类中的字段。字段名称与参数名称相同,字段类型与参数类型相同或为 dynamic
如果未指定。可以使用 @WithType
注解覆盖参数类型,例如当构建时无法获得类型信息时。
要改变子类名称的前缀(默认为顶级类名),可以使用 @WithPrefix
注解。例如:
@Sealed()
@WithPrefix('Hello')
abstract class _Weather {
void sunny();
}
现在 sunny
将被命名为 HelloSunny
而不是默认的 WeatherSunny
。你可以使用 @WithPrefix('')
来移除所有子类名称的前缀。
要直接改变子类名称,可以使用 @WithName
注解。如果指定了它将覆盖 WithPrefix
。例如:
@Sealed()
abstract class _Weather {
@WithName('Hello')
void sunny();
}
现在 sunny
将被命名为 Hello
而不是默认的 WeatherSunny
。这对于不想使用前缀的情况很有用。
几乎所有密封类上的方法都使用从清单方法名提取的简短名称。不使用全子类名称。建议不要直接使用子类。超类上有每个项目的工厂方法。
泛型使用
对于泛型密封类,你应该像实现通用类一样编写清单类。
推荐方式是,如果你想使用可空的泛型字段,声明一个泛型参数为 T extends Base?
并使用 T
不带可空后缀。如果你想使用不可空的泛型字段,声明一个泛型参数为 T extends Base
并使用 T
不带可空后缀。如果你没有指定上界,它将默认为 Object?
,因此你的泛型类型将是可空的。
import 'package:sealed_annotations/sealed_annotations.dart';
part 'result.sealed.dart';
@Sealed()
abstract class _Result<D extends num> {
void success(D data);
void error(Object exception);
}
或者你可以有多个泛型类型并混合它们。
import 'package:sealed_annotations/sealed_annotations.dart';
part 'result.sealed.dart';
@Sealed()
abstract class _Result<D extends num, E extends Object> {
void success(D data);
void error(E exception);
void mixed(D data, E exception);
}
动态类型和在一个密封类型中使用另一个密封类型
考虑你有一个密封结果类型,如下:
@Sealed()
abstract class _Result<D extends Object> {
/* ... */
}
你想在这个类型中使用另一个密封类型。
@Sealed()
abstract class _WeatherInfo {
void fromInternet(Result<WeatherData> result);
}
如果你为 _WeatherInfo
生成代码,你会看到结果类型是 dynamic
。这是因为 Result
本身在构建时未生成代码。
你应该使用 @WithType
注解。
@Sealed()
abstract class _WeatherInfo {
void fromInternet(@WithType('Result<WeatherData>') result);
// 你也可以有可空类型。
void nullable(@WithType('Result<WeatherData>?') result);
}
层次特性
如果密封类在同一文件中,你可以直接引用它们,使用它们的清单类名。这是为了避免 @WithType
注解并提高重构能力。
@Sealed()
abstract class _Apple {
void eat();
}
@Sealed()
abstract class _Banana {
void eat();
}
@Sealed()
abstract class _Basket {
void friends(_Apple? apple, _Banana? banana);
// 或等效地:
// void friends(@WithType('Apple?') apple, @WithType('Banana?') banana);
}
对于泛型情况:
@Sealed()
abstract class _Result<D extends num> {
void success(D data);
void error(Object exception);
}
@Sealed()
abstract class _Basket {
void hold(_Result<int> x);
// 或等效地:
// void hold(@WithType('Result<int>') x);
}
@WithType
注解会覆盖层次特性。
公共字段
有时你需要一些字段存在于所有密封类中。例如,考虑制作一个用于不同错误类型的密封类,所有这些都需要 code
和 message
。手动添加 code
和 message
到所有密封类非常烦人。如果你有一个错误对象,你无法在不使用类型转换或匹配方法的情况下获取其 code
或 message
。在这里你可以使用公共字段。
要声明一个公共字段,可以在清单类中添加一个getter或final字段,并且它将自动添加到所有密封类中。例如:
@Sealed()
abstract class _ApiError {
// 使用getter
String get message;
// 使用final字段
final String? code = null;
// code和message将自动添加到这里
void internetError();
void badRequest();
void internalError(Object? error);
}
你也可以使用构造函数与final字段等效。
公共字段在 ApiError
对象及其子类中都可用。
如果你在密封类中指定公共字段则无效。例如:
@Sealed()
abstract class _Common {
Object get x;
// one和two将具有相同的签名
void one(Object x);
void two();
}
你可以使用公共字段类型的子类在密封类中。例如:
@Sealed()
abstract class _Common {
Object get x;
// x的类型为int
void one(int x);
// x的类型为String
void one(String x);
// x的类型为Object
void three();
}
公共字段也适用于其他Dart密封构造,如泛型和 @WithType
。例如:
@Sealed()
abstract class _Common {
@WithType('num')
dynamic get x; // 你可以省略dynamic
// x的类型为int
void one(@WithType('int') dynamic x); // 你可以省略dynamic
// x的类型为num
void two();
}
并且,例如:
@Sealed()
abstract class _Result<D extends num> {
Object? get value;
void success(D value);
void error();
}
忽略生成的文件
建议在Git中忽略生成的文件。在你的 .gitignore
文件中添加:
*.sealed.dart
为了排除分析器中的生成文件,在你的 analysis_options.yaml
文件中添加:
analyzer:
exclude:
- lib/**/*.sealed.dart
完整示例
以下是完整的示例代码:
import 'result.dart';
import 'weather.dart';
void main() {
final a = Weather.sunny();
final b = Weather.rainy(rain: 12);
final c = Weather.windy(velocity: 1.5, angle: null);
print(a);
print(b);
print(c);
final d = Result.success(data: 1);
final e = Result.success(data: 5.6);
final f = Result.error(exception: 'error');
print(d);
print(e);
print(f);
}
更多关于Flutter代码生成插件sealed_generators的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter代码生成插件sealed_generators的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
sealed_generators
是一个用于 Flutter 和 Dart 的代码生成插件,它可以帮助你自动生成与密封类(sealed class)相关的代码。密封类是一种特殊的类,它的所有子类在编译时都是已知的,这有助于在代码中实现更严格的类型检查和模式匹配。
安装 sealed_generators
首先,你需要在 pubspec.yaml
中添加 sealed_generators
作为开发依赖项:
dev_dependencies:
sealed_generators: ^1.0.0
build_runner: ^2.1.0
使用 sealed_generators
1. 定义密封类
首先,你需要定义一个密封类。密封类通常是一个抽象类,并且它的所有子类都必须在同一个文件中定义。
import 'package:sealed_annotations/sealed_annotations.dart';
part 'example.sealed.dart';
@Sealed()
abstract class Result<T> {}
class Success<T> extends Result<T> {
final T value;
Success(this.value);
}
class Failure<T> extends Result<T> {
final Exception error;
Failure(this.error);
}
2. 运行代码生成器
在终端中运行以下命令来生成代码:
flutter pub run build_runner build
这将会生成一个名为 example.sealed.dart
的文件,其中包含了与密封类相关的代码。
3. 使用生成的代码
生成的代码将包含一些有用的工具,例如 when
和 maybeWhen
方法,它们可以帮助你在密封类的子类上进行模式匹配。
void main() {
Result<int> result = Success(42);
result.when(
success: (value) => print('Success: $value'),
failure: (error) => print('Failure: $error'),
);
// Output: Success: 42
}
生成的代码示例
example.sealed.dart
文件可能包含以下内容:
part of 'example.dart';
extension ResultWhen<T> on Result<T> {
R when<R>({
required R Function(Success<T>) success,
required R Function(Failure<T>) failure,
}) {
if (this is Success<T>) {
return success(this as Success<T>);
} else if (this is Failure<T>) {
return failure(this as Failure<T>);
} else {
throw Exception('Unknown subclass of Result');
}
}
R maybeWhen<R>({
R Function(Success<T>)? success,
R Function(Failure<T>)? failure,
required R Function() orElse,
}) {
if (this is Success<T> && success != null) {
return success(this as Success<T>);
} else if (this is Failure<T> && failure != null) {
return failure(this as Failure<T>);
} else {
return orElse();
}
}
}