Flutter注解插件super_enum_sealed_annotations的使用

Flutter注解插件super_enum_sealed_annotations的使用

生成用于Dart和Flutter的密封类层次结构,适用于null-safe和非null-safe项目。

特性

  • 生成具有抽象超类型和数据子类的密封类。
  • 静态工厂方法。例如 Result.success(data: 0)
  • 类型转换方法。例如 a.asSuccess()a.isSuccess()a.asSuccessOrNull()
  • 三种类型的相等性和hashCode生成:数据(类似于Kotlin数据类)、身份和唯一。
  • 使用流行的equatable库实现数据相等性。
  • 支持泛型。即使类型可以混合。
  • 在null-safe项目中支持可空和不可空类型。
  • 支持在一个密封类型中使用另一个密封类型。
  • 同时支持非null-safe和null-safe项目。
  • 为非null-safe项目生成可空性注释以简化迁移。
  • 为数据类生成toString方法。
  • 生成六种不同匹配方法。例如 whenwhenOrElse
  • 支持自动拆包和继承。

使用

pubspec.yaml 文件中添加依赖:

dependencies:
  sealed_annotations: ^latest.version

dev_dependencies:
  sealed_generators: ^latest.version

导入 sealed_annotations

import 'package:sealed_annotations/sealed_annotations.dart';

添加 part 指向一个文件,该文件将包含生成的类,扩展名为 .super.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);
}

然后运行以下命令来生成代码:

dart run build_runner build

生成的代码将如下所示(以下是总结):

abstract class Weather {
  const factory Weather.rainy({required int rain}) = WeatherRainy;

  bool isRainy() => this is WeatherRainy;

  WeatherRainy asRainy() => this as WeatherRainy;

  WeatherRainy? asRainyOrNull() {
    /* ... */
  }

  R when<R extends Object?>({
    required R Function(WeatherSunny sunny) sunny,
    required R Function(WeatherRainy rainy) rainy,
    required R Function(WeatherWindy windy) windy,
  }) {
    /* ... */
  }

  R whenOrElse<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 whenOrDefault<R extends Object?>({
    R Function(WeatherSunny sunny)? sunny,
    R Function(WeatherRainy rainy)? rainy,
    R Function(WeatherWindy windy)? windy,
    required R orDefault,
  }) {
    /* ... */
  }

  R? whenOrNull<R extends Object?>({
    R Function(WeatherSunny sunny)? sunny,
    R Function(WeatherRainy rainy)? rainy,
    R Function(WeatherWindy windy)? windy,
  }) {
    /* ... */
  }

  R whenOrThrow<R extends Object?>({
    R Function(WeatherSunny sunny)? sunny,
    R Function(WeatherRainy rainy)? rainy,
    R Function(WeatherWindy windy)? windy,
  }) {
    /* ... */
  }

  void whenPartial({
    void Function(WeatherSunny sunny)? sunny,
    void Function(WeatherRainy rainy)? rainy,
    void Function(WeatherWindy windy)? windy,
  }) {
    /* ... */
  }
}

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 {
  /* ... */
}

注意事项:

  • 总是使用你需要的确切匹配方法,例如不要用 whenOrNull 而是用 whenPartial
  • 更喜欢在超类中使用工厂而不是子类构造函数。
  • 尽量减少使用类型转换方法,大多数情况下可以用匹配方法代替。

相等性和生成类名

你可以通过 [@WithEquality](/user/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](/user/WithEquality)(Equality.identity)
abstract class _Weather {
  void sunny();

  void rainy(int rain);

  [@WithEquality](/user/WithEquality)(Equality.distinct)
  void windy(double velocity, double? angle);
}

一个抽象超类将被生成,其名称等于清单类名称(这里为 Weather)。每个方法都会成为一个子类。应该至少有一个方法。子类名称基于方法名称并前缀于超类名称(例如 WeatherSunny)。命名过程可以通过使用 @WithPrefix[@WithName](/user/WithName) 注解进行定制。每个方法参数都将成为相应子类中的字段。字段名称与参数名称相同,字段类型与参数类型相同或动态,如果未指定。参数类型可以在构建时使用 @WithType 注解覆盖。请注意,你可以有可空和不可空字段。在非null-safe项目中,所有字段都被视为可空。

要更改子类名称的前缀(默认为顶级类名称),你可以使用 @WithPrefix 注解。例如:

[@Sealed](/user/Sealed)()
@WithPrefix('Hello')
abstract class _Weather {
  void sunny();
}

现在 sunny 将被命名为 HelloSunny 而不是默认的 WeatherSunny。你可以使用 @WithPrefix('') 来移除所有前缀。

要直接更改子类名称,你可以使用 [@WithName](/user/WithName) 注解。它会覆盖 WithPrefix 如果已指定。例如:

[@Sealed](/user/Sealed)()
abstract class _Weather {
  [@WithName](/user/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](/user/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](/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);
}

包装和继承

有时你会使用一个密封类型只是为了包装一些字段,然后在例如你的BLoC中使用匹配函数来解开它。在这种情况下,最好让密封子类不明确出现。

例如假设以下清单:

[@Sealed](/user/Sealed)()
abstract class State {
  void initial();

  void loading();

  void data(Object obj);
}

当你想在这个上面应用 when 时,它看起来像这样:

Object doTest(State s) {
  return s.when(
    initial: (initial) => 'something',
    loading: (loading) => 'other thing',
    data: (data) => 'data ${data.obj}',
  );
}

正如你所见,你甚至不需要 initialloading 值,甚至你只需要 data 值来从其中提取 obj

你可以使用 @WithWrap() 注解:

[@Sealed](/user/Sealed)()
@WithWrap()
abstract class State {
  void initial();

  void loading();

  void data(Object obj);
}

那么你的匹配函数将看起来像这样:

Object doTest(State s) {
  return s.when(
    initial: () => 'something',
    loading: () => 'other thing',
    data: (obj) => 'data $obj',
  );
}

如你所见,它会为你自动解开值。如果你在类上添加 @WithWrap(),它将应用于所有子类。如果你只想为某些子类应用它,可以在方法上应用它们。例如:

[@Sealed](/user/Sealed)()
abstract class State {
  @WithWrap()
  void initial();

  @WithWrap()
  void loading();

  void data(Object obj);
}

在上述示例中,data 不会被自动解开。

此外,当创建一个密封子类时,其构造函数和相应的密封超类中的工厂将使用位置参数而不是命名参数。例如对于 data

// 当未包装时:
final d1 = State.data(obj: 'hello');

// 当包装时:
final d2 = State.data('hello');

动态类型和在一个密封类型中使用另一个密封类型

假设你有一个密封结果类型如下:

[@Sealed](/user/Sealed)()
abstract class _Result<D extends Object> {
  /* ... */
}

你想在另一个密封类型中使用这种类型。

[@Sealed](/user/Sealed)()
abstract class _WeatherInfo {
  void fromInternet(Result<WeatherData> result);
}

如果你为 _WeatherInfo 生成代码,你会看到结果具有 dynamic 类型。这是因为 Result 本身在构建时没有被代码生成。

你应该使用 @WithType 注解。

[@Sealed](/user/Sealed)()
abstract class _WeatherInfo {
  void fromInternet(@WithType('Result<WeatherData>') result);

  // 你也可以有可空类型。
  void nullable(@WithType('Result<WeatherData>?') result);
}

super_enum兼容API

你可以使用 super_enum_sealed_annotationssuper_enum_sealed_generators 代替 super_enumsuper_enum_generator。它具有 super_enumdart_sealed 的所有功能,除了 @WithType 用于动态类型。它还可以在非null-safe和null-safe项目中使用。除了更改依赖项外,你还应该只将 super_enum 的导入更改为 super_enum_sealed_annotations。可能还需要对你的一些代码进行其他更改。

有关更多信息,请参阅 super_enum 文档: super_enum

从super_enum迁移

如果你正在使用 super_enum,并且现在想将你的依赖项更改为 dart_sealed

  • super_enum 的依赖项更改为 super_enum_sealed_annotations
  • 添加 sealed_annotations 依赖项。
  • super_enum_generator 的依赖项更改为 super_enum_sealed_generators
  • 添加 sealed_generators 的开发依赖项。
  • super_enum 的导入更改为 super_enum_sealed_annotations
  • 运行 dart run build_runner build
  • 现在,我们的库将在 .super 文件中为你生成代码。除了生成的密封类之外,还会生成一些注释以指导你完成迁移,以及带有 [@Sealed](/user/Sealed) 注解的相同名称的清单类。
  • 在这里可能会有一些需要解决的冲突。
  • 取消注释并替换生成的清单类(带有 [@Sealed](/user/Sealed) 注解),然后如果你的项目是null-safe,开始根据需要更改每个字段的可空性。在非null-safe项目中,字段被视为可空,但如果你想,你可以更改可空性注释以在迁移时简化工作。如果密封类是泛型,更改 Generic 类型参数名称,如果需要使用多个类型参数。大多数时候不需要 [@WithEquality](/user/WithEquality)[@WithName](/user/WithName),但它们会被自动生成。你可以根据需要删除或更改它们。
  • super_enum_sealed_annotations 的导入更改为 sealed_annotations
  • part 后缀从 .super 更改为 .sealed
  • 运行 dart run build_runner build
  • 删除 .super 文件(如果未自动删除)。
  • 移除 super_enum_sealed_annotations 依赖项。
  • 移除 super_enum_sealed_generators 依赖项。
  • 可选:如果你想将代码迁移到null-safe,现在应该使用标准迁移工具。然后再次运行 dart run build_runner build,这个库会为你处理它。

例如对于:

@superEnum
enum _Weather {
  @object
  Sunny,
  @Data(fields: [
    DataField<int>('rain'),
  ])
  Rainy,
  @Data(fields: [
    DataField<double>('velocity'),
    DataField<double>('angle', required: false),
  ])
  Windy,
}

它将生成密封类和以下清单类。

[@Sealed](/user/Sealed)()
abstract class _Weather$ {
  [@WithEquality](/user/WithEquality)(Equality.data)
  [@WithName](/user/WithName)('Sunny')
  void sunny();

  [@WithEquality](/user/WithEquality)(Equality.data)
  [@WithName](/user/WithName)('Rainy')
  void rainy(int rain);

  [@WithEquality](/user/WithEquality)(Equality.data)
  [@WithName](/user/WithName)('Windy')
  void windy(double velocity, double? angle);
}

更多关于Flutter注解插件super_enum_sealed_annotations的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter注解插件super_enum_sealed_annotations的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


super_enum_sealed_annotations 是一个用于 Flutter 的注解插件,它可以帮助开发者更方便地使用 super_enumsealed 类。这个插件通过注解的方式减少了样板代码的编写,并且提供了类型安全的枚举和密封类的实现。

安装

首先,你需要在 pubspec.yaml 文件中添加 super_enum_sealed_annotationsbuild_runner 作为依赖项:

dependencies:
  flutter:
    sdk: flutter
  super_enum_sealed_annotations: ^1.0.0

dev_dependencies:
  build_runner: ^2.1.0

使用步骤

  1. 定义枚举类

    使用 [@superEnum](/user/superEnum) 注解来定义一个枚举类。这个注解会生成一个类型安全的枚举类。

    import 'package:super_enum_sealed_annotations/super_enum_sealed_annotations.dart';
    
    [@superEnum](/user/superEnum)
    enum Status {
      @Data(fields: [])
      Loading,
      @Data(fields: [])
      Success,
      @Data(fields: [])
      Error,
    }
    
  2. 生成代码

    运行 build_runner 来生成代码:

    flutter pub run build_runner build
    

    这会生成一个 status.g.dart 文件,其中包含了 Status 枚举类的实现。

  3. 使用生成的枚举类

    现在你可以使用生成的枚举类了:

    void main() {
      var status = Status.loading();
    
      switch (status) {
        case Loading():
          print('Loading...');
          break;
        case Success():
          print('Success!');
          break;
        case Error():
          print('Error!');
          break;
      }
    }
    

定义密封类

super_enum_sealed_annotations 也支持定义密封类。密封类是一种特殊的抽象类,它限制了子类的数量,并且可以在 switch 语句中进行模式匹配。

  1. 定义密封类

    使用 [@sealed](/user/sealed) 注解来定义一个密封类:

    import 'package:super_enum_sealed_annotations/super_enum_sealed_annotations.dart';
    
    [@sealed](/user/sealed)
    abstract class Result {}
    
    [@data](/user/data)
    class Success extends Result {
      final String data;
      Success(this.data);
    }
    
    [@data](/user/data)
    class Error extends Result {
      final String message;
      Error(this.message);
    }
    
  2. 生成代码

    运行 build_runner 来生成代码:

    flutter pub run build_runner build
    

    这会生成一个 result.g.dart 文件,其中包含了 Result 密封类的实现。

  3. 使用生成的密封类

    现在你可以使用生成的密封类了:

    void main() {
      Result result = Success('Data loaded successfully');
    
      switch (result) {
        case Success(data: final data):
          print('Success: $data');
          break;
        case Error(message: final message):
          print('Error: $message');
          break;
      }
    }
回到顶部