Flutter密封类生成插件sealed_class_writer的使用
Flutter密封类生成插件sealed_class_writer的使用
Dart密封类生成器
在本指南中,我们将展示如何使用 sealed_class_writer
插件来生成 Dart 和 Flutter 中的密封类。
生成 Dart 和 Flutter 的密封类层次结构。
特性
- 生成具有抽象超类型和数据子类的密封类。
- 静态工厂方法。例如
Result.success(data: 0)
。 - 类型转换方法。例如
a.asSuccess
,a.isSuccess
或a.asSuccessOrNull
。 - 三种类型的相等性和哈希码生成:数据(类似于 Kotlin 数据类)、标识和唯一。
- 使用流行的
equatable
库实现数据相等性。 - 支持泛型。即使类型可以混合。
- 在 null 安全项目中支持可空和不可空类型。
- 支持在一个密封类型中使用另一个密封类型。
- 支持 null 安全。
- 为数据类生成
toString
方法。 - 生成六种不同匹配方法。如
when
,maybeWhen
和map
。
使用方法
添加依赖
在你的 pubspec.yaml
文件中添加以下依赖项:
dependencies:
sealed_class_annotations: ^latest.version
dev_dependencies:
sealed_generators: ^latest.version
导入库
导入 sealed_class_annotations
:
import 'package:sealed_class_annotations/sealed_class_annotations.dart';
分割文件
添加 part
指向你希望生成类的文件,并以 .sealed.dart
扩展名结尾:
part 'weather.sealed.dart';
添加密封类注解
添加 [@Sealed](/user/Sealed)
注解,并定义一个抽象私有类作为生成代码的清单。例如:
[@Sealed](/user/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 {
/* ... */
}
注意事项
- 尽量使用超类中的工厂方法而不是子类构造函数。例如,使用
Weather.rainy()
而不是WeatherRainy()
。 - 尽量减少使用类型转换方法,大多数情况下可以通过匹配方法替代。
相等性和生成类名
你可以通过 @WithEquality(...)
注解选择三种类型的相等性。默认相等性为 data
,如果未指定。这将成为所有子类的默认相等性。你可以在每个方法上使用此注解来更改单个子类的相等性。
相等性类型:
data
:相等性通过Equatable
包实现。行为类似于 Kotlin 数据类。identity
:只有完全相同的实例才相等。类似于未实现任何特定相等性的行为。distinct
:所有实例都不相等。甚至实例也不等于自身。
基本示例:
[@Sealed](/user/Sealed)()
abstract class _Weather {
void sunny();
void rainy(int rain);
void windy(double velocity, double? angle);
}
在这个示例中,所有类都将具有 data
相等性。如果你想让所有类都具有 identity
相等性,但 windy
具有 distinct
相等性:
[@Sealed](/user/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](/user/WithType)
注解覆盖,例如当构建时无法获取类型信息时。请注意,你可以有可空和不可空字段。
要更改子类名称的前缀,默认为顶级类名,你可以使用 @WithPrefix
注解。例如:
[@Sealed](/user/Sealed)()
@WithPrefix('Hello')
abstract class _Weather {
void sunny();
}
现在 sunny
将命名为 HelloSunny
而不是默认的 WeatherSunny
。你可以使用 @WithPrefix('')
来从子类名称中移除所有前缀。
要直接更改子类名称,你可以使用 @WithName
注解。它会覆盖 WithPrefix
如果指定。例如:
[@Sealed](/user/Sealed)()
abstract class _Weather {
@WithName('Hello')
void sunny();
}
``
现在 `sunny` 将命名为 `Hello` 而不是默认的 `WeatherSunny`。这在你不想为某些项目使用前缀时非常有用。
几乎所有密封类上的方法都使用从清单方法名称提取的短名称。不使用完整的子类名称。建议不要直接使用子类。在超类上有每个项目的工厂方法。
### 泛型使用
对于泛型密封类,你应该像实现泛型类一样编写清单类。
建议,如果你想要可空的泛型字段,声明一个泛型参数为 `T extends Base?` 并使用 `T` 而不带可空后缀。如果你想要不可空的泛型字段,声明一个泛型参数为 `T extends Base` 并使用 `T` 而不带可空后缀。如果你不指定上界,它将默认为 `Object?`,因此你的泛型类型将是可空的。
```dart
import 'package:sealed_class_annotations/sealed_class_annotations.dart';
part 'result.sealed.dart';
[@Sealed](/user/Sealed)()
abstract class _Result<D extends num> {
void success(D data);
void error(Object exception);
}
或者你可以有多个泛型类型并混合它们。
import 'package:sealed_class_annotations/sealed_class_annotations.dart';
part 'result.sealed.dart';
[@Sealed](/user/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](/user/Sealed)()
abstract class _Result<D extends Object> {
/* ... */
}
你想在这个类型中使用另一个密封类型。
[@Sealed](/user/Sealed)()
abstract class _WeatherInfo {
void fromInternet(Result<WeatherData> result);
}
如果你为 _WeatherInfo
生成代码,你会发现结果具有 dynamic
类型。这是因为 Result
本身在构建时未被代码生成。
你应该使用 [@WithType](/user/WithType)
注解。
[@Sealed](/user/Sealed)()
abstract class _WeatherInfo {
void fromInternet([@WithType](/user/WithType)('Result<WeatherData>') result);
// 你也可以有可空类型。
void nullable([@WithType](/user/WithType)('Result<WeatherData>?') result);
}
层次特性
如果密封类在同一文件中,你可以直接引用它们的清单类名称。这是为了避免 [@WithType](/user/WithType)
注解并提高重构能力。
[@Sealed](/user/Sealed)()
abstract class _Apple {
void eat();
}
[@Sealed](/user/Sealed)()
abstract class _Banana {
void eat();
}
[@Sealed](/user/Sealed)()
abstract class _Basket {
void friends(_Apple? apple, _Banana? banana);
// 或等效地:
// void friends([@WithType](/user/WithType)('Apple?') apple, [@WithType](/user/WithType)('Banana?') banana);
}
``
对于泛型情况:
```dart
[@Sealed](/user/Sealed)()
abstract class _Result<D extends num> {
void success(D data);
void error(Object exception);
}
[@Sealed](/user/Sealed)()
abstract class _Basket {
void hold(_Result<int> x);
// 或等效地:
// void hold([@WithType](/user/WithType)('Result<int>') x);
}
``
`[@WithType](/user/WithType)` 注解会覆盖层次特性如果存在。
### 公共字段
有时你需要一些字段存在于所有的密封类中。例如,考虑制作一个不同的错误类型的密封类,所有这些错误都需要有 `code` 和 `message`。手动为所有密封类添加 `code` 和 `message` 非常烦人。而且如果你有一个错误对象,你无法在不使用类型转换或匹配方法的情况下获取其代码或消息。在这里,你可以使用公共字段。
要声明一个公共字段,你可以在清单类中添加一个 getter 或一个最终字段,它将自动添加到所有密封类中。例如:
```dart
[@Sealed](/user/Sealed)()
abstract class _ApiError {
// 使用 getter
String get message;
// 使用最终字段
final String? code = null;
// code 和 message 将自动添加到这里
void internetError();
void badRequest();
void internalError(Object? error);
}
你也可以用最终字段与构造函数配对等效地使用。
公共字段在 ApiError
对象及其子类中都可用。
如果你在密封类中指定了公共字段,它不会产生影响。例如:
[@Sealed](/user/Sealed)()
abstract class _Common {
Object get x;
// one 和 two 将有相同的签名
void one(Object x);
void two();
}
你可以使用公共字段类型的子类在密封类中。例如:
[@Sealed](/user/Sealed)()
abstract class _Common {
Object get x;
// x 的类型为 int
void one(int x);
// x 的类型为 String
void one(String x);
// x 的类型为 Object
void three();
}
公共字段也适用于其他 Dart_sealed 构造,如泛型和 [@WithType](/user/WithType)
。例如:
[@Sealed](/user/Sealed)()
abstract class _Common {
[@WithType](/user/WithType)('num')
dynamic get x; // 你可以省略 dynamic
// x 的类型为 int
void one([@WithType](/user/WithType)('int') dynamic x); // 你可以省略 dynamic
// x 的类型为 num
void two();
}
例如:
[@Sealed](/user/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
示例代码
以下是一个完整的示例代码,展示了如何使用 sealed_class_writer
插件生成密封类。
import 'package:sealed_class_writer/sealed_class_writer.dart';
void main() {
final source = Manifest(
name: 'Weather',
items: [
ManifestItem(
name: 'WeatherSunny',
shortName: 'sunny',
equality: ManifestEquality.data,
fields: [],
),
ManifestItem(
name: 'WeatherRainy',
shortName: 'rainy',
equality: ManifestEquality.data,
fields: [
ManifestField(
name: 'rain',
type: ManifestType(
name: 'int',
isNullable: false,
),
),
],
),
],
params: [],
fields: [],
);
print('==============');
print('FORWARD WRITER');
print('==============');
final forward = SourceWriter(source, referToManifest: false);
print(forward.write());
print('===============');
print('BACKWARD WRITER');
print('===============');
final backward = BackwardWriter(source);
print(backward.write());
}
更多关于Flutter密封类生成插件sealed_class_writer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter密封类生成插件sealed_class_writer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用sealed_class_writer
插件来生成密封类(Sealed Classes)的详细步骤和代码示例。
安装sealed_class_writer
插件
首先,你需要在你的Flutter项目的pubspec.yaml
文件中添加sealed_class_writer
依赖项:
dependencies:
flutter:
sdk: flutter
sealed_class_writer: ^最新版本号 # 请替换为最新版本号
然后运行以下命令来安装依赖:
flutter pub get
配置Dart插件
sealed_class_writer
是一个Dart代码生成插件,因此你需要在你的项目中创建一些配置文件。
-
创建配置文件:
在你的项目根目录下创建一个名为
build.yaml
的文件,并添加以下内容:targets: $default: builders: sealed_class_writer: enabled: true
-
创建密封类模板文件:
在你的
lib
目录下创建一个名为sealed_classes
的文件夹,并在其中创建一个模板文件,例如example_sealed_class.dart
。这个文件将包含你的密封类的定义。// [@sealed](/user/sealed) abstract class MySealedClass { const MySealedClass(); // You can define abstract methods or properties here abstract void doSomething(); } class SubClassOne extends MySealedClass { const SubClassOne(); @override void doSomething() { // Implementation } } class SubClassTwo extends MySealedClass { const SubClassTwo(); @override void doSomething() { // Implementation } }
注意:虽然
[@sealed](/user/sealed)
注解在这里不会被直接处理,但它可以帮助你和其他开发者识别这是一个密封类。
使用密封类
在配置完成后,你可以使用flutter pub run build_runner build
命令来生成密封类的工厂方法或相关辅助代码(具体取决于插件的实现细节)。
不过,由于sealed_class_writer
的具体实现细节可能因版本而异,且这个插件可能并不直接生成标准的Dart密封类(因为Dart本身不支持密封类的概念),你可能需要手动编写一些辅助代码来模拟密封类的行为。
示例:手动实现密封类模式
虽然sealed_class_writer
可能无法直接生成Dart中的密封类,但你可以通过枚举和工厂方法来实现类似的功能。以下是一个手动实现密封类模式的示例:
enum MySealedClassKind {
SubClassOne,
SubClassTwo,
}
abstract class MySealedClass {
final MySealedClassKind kind;
const MySealedClass(this.kind);
void doSomething();
// Factory method to create instances of the sealed class
static MySealedClass fromKind(MySealedClassKind kind) {
switch (kind) {
case MySealedClassKind.SubClassOne:
return SubClassOne();
case MySealedClassKind.SubClassTwo:
return SubClassTwo();
default:
throw UnsupportedError('Unknown kind: $kind');
}
}
}
class SubClassOne extends MySealedClass {
const SubClassOne() : super(MySealedClassKind.SubClassOne);
@override
void doSomething() {
// Implementation for SubClassOne
}
}
class SubClassTwo extends MySealedClass {
const SubClassTwo() : super(MySealedClassKind.SubClassTwo);
@override
void doSomething() {
// Implementation for SubClassTwo
}
}
// Usage
void main() {
MySealedClass instance = MySealedClass.fromKind(MySealedClassKind.SubClassOne);
instance.doSomething();
}
这个示例通过枚举和工厂方法模拟了密封类的行为,确保了类的实例只能是预定义的几个子类之一。
总结
虽然sealed_class_writer
插件可能无法直接生成Dart中的密封类,但你可以通过配置文件和手动代码来实现类似的功能。希望这个示例能帮助你在Flutter项目中更好地使用密封类模式。