Flutter密封类生成插件sealed_class_writer的使用

发布于 1周前 作者 itying888 来自 Flutter

Flutter密封类生成插件sealed_class_writer的使用

Dart密封类生成器

在本指南中,我们将展示如何使用 sealed_class_writer 插件来生成 Dart 和 Flutter 中的密封类。

生成 Dart 和 Flutter 的密封类层次结构。

特性

  • 生成具有抽象超类型和数据子类的密封类。
  • 静态工厂方法。例如 Result.success(data: 0)
  • 类型转换方法。例如 a.asSuccessa.isSuccessa.asSuccessOrNull
  • 三种类型的相等性和哈希码生成:数据(类似于 Kotlin 数据类)、标识和唯一。
  • 使用流行的 equatable 库实现数据相等性。
  • 支持泛型。即使类型可以混合。
  • 在 null 安全项目中支持可空和不可空类型。
  • 支持在一个密封类型中使用另一个密封类型。
  • 支持 null 安全。
  • 为数据类生成 toString 方法。
  • 生成六种不同匹配方法。如 whenmaybeWhenmap

使用方法

添加依赖

在你的 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

1 回复

更多关于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代码生成插件,因此你需要在你的项目中创建一些配置文件。

  1. 创建配置文件

    在你的项目根目录下创建一个名为build.yaml的文件,并添加以下内容:

    targets:
      $default:
        builders:
          sealed_class_writer:
            enabled: true
    
  2. 创建密封类模板文件

    在你的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项目中更好地使用密封类模式。

回到顶部