Dart 密封类生成器
为 Dart 和 Flutter 生成密封类层次结构。
特点
- 生成带有抽象超类型和数据子类的密封类。
- 静态工厂方法。例如
Result.success(data: 0)。 - 类型转换方法。例如
a.asSuccess、a.isSuccess或a.asSuccessOrNull。 - 三种等值和 hashCode 生成方式:数据(类似 kotlin 的 data classes)、标识和独立。
- 使用流行的 equatable 库实现数据等值。
- 支持泛型。甚至可以混合类型。
- 支持空安全项目中的可空和非空类型。
- 支持在一个密封类型中使用另一个。
- 支持空安全。
- 为数据类生成 toString。
- 生成 6 种不同的匹配方法。例如
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,
}) {
/* ... */
}
void partialWhen({
void Function()? sunny,
void Function(int rain)? rainy,
void Function(double velocity, double? angle)? windy,
void 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,
}) {
/* ... */
}
void partialMap({
void Function(WeatherSunny sunny)? sunny,
void Function(WeatherRainy rainy)? rainy,
void Function(WeatherWindy windy)? windy,
void 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 的 data classes。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 注释来定制命名过程。每个方法的参数将成为相应子类中的字段。字段名称等于
参数名称,字段类型等于参数类型或动态(如果未指定)。参数类型可以
通过 @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 生成,您会看到 result 的类型是 dynamic。这是因为 Result 本身不是代码
在构建时生成。
您应该使用 @WithType 注释。
@Sealed()
abstract class _WeatherInfo {
void fromInternet(@WithType('Result<WeatherData>') result);
// you can also have nullable types.
void nullable(@WithType('Result<WeatherData>?') result);
}
通用字段
有时您需要一些字段存在于所有密封类中。例如,考虑为不同类型的错误创建密封类,
并且所有这些都必须具有 code 和 message。手动为所有密封类添加代码和消息会非常烦人。此外,如果您有一个错误对象,您无法在不使用类型转换或匹配方法的情况下获取其代码或消息。在这里,您可以使用通用字段。
您无法在不使用类型转换或匹配方法的情况下获取其代码或消息。
您可以使用通用字段。
要声明通用字段,您可以向清单类添加一个 getter 或一个 final 字段,它将自动添加到您所有的密封类中。例如
通用字段也可以在 ApiError 对象及其子类上使用。
@Sealed()
abstract class _ApiError {
// using getter
String get message;
// using final field
final String? code = null;
// code and message will be added to this automatically
void internetError();
void badRequest();
void internalError(Object? error);
}
通用字段也可以在 ApiError 对象及其子类上访问。
如果您在密封类中指定通用字段,则无效。例如
@Sealed()
abstract class _Common {
Object get x;
// one and two will have identical signatures
void one(Object x);
void two();
}
您可以在密封类中使用通用字段的子类。例如
@Sealed()
abstract class _Common {
Object get x;
// x has type int
void one(int x);
// x has type String
void one(String x);
// x has type Object
void three();
}
通用字段也与其他 dart_sealed 结构(如泛型和 @WithType)配合使用。例如
@Sealed()
abstract class _Common {
@WithType('num')
dynamic get x; // you can omit dynamic
// x has type int
void one(@WithType('int') dynamic x); // you can omit dynamic
// x has type 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:
- **.sealed.dart