🧊

[Flutter/Dart] Freezed 3.0 がリリース! 2.x からの主要な変更点まとめ

に公開

最近(といっても2ヶ月位前っぽい)リリースされた Freezed 3.0 について、 2.x からの更新作業にあたって主要な変更点を調べてまとめました。[1]

破壊的変更

sealed または abstract の必須化

factory コンストラクタを用いるクラスには abstract (単一クラスの場合) または sealed (直和型(Union)の場合) が必要になりました。

@freezed
-class Person with _$Person {
+abstract class Person with _$Person {
  const factory Person({
    required String firstName,
    required String lastName,
    required int age,
  }) = _Person;

  factory Person.fromJson(Map<String, Object?> json)
      => _$PersonFromJson(json);
}
@freezed
-class Model with _$Model {
+sealed class Model with _$Model {
  factory Model.first(String a) = First;
  factory Model.second(int b, bool c) = Second;
}

map when と、その派生メソッドが削除

Dart にパターンマッチングが導入されたことにより非推奨となっていた mapwhen などのメソッドが削除されました。

解決策

Dart のパターンマッチングを用いるように書き換えてください。

final model = Model.first('42');

-final res = model.map(
-  first: (String a) => 'first $a',
-  second: (int b, bool c) => 'second $b $c',
-);
+final res = switch (model) {
+  First(:final a) => 'first $a',
+  Second(:final b, :final c) => 'second $b $c',
+};

新規要素

Mixed mode

mixed mode という定義方法ができるようになりました。

従来の factory コンストラクタを用いた定義方法では、 Freezed がコンストラクタやプロパティを生成していました。
mixed mode では、コンストラクタやプロパティは開発者が定義することができます。

これまでの定義の仕方 (Factory コンストラクタ)


sealed class Usual with _$Usual {
  // Freezed がコンストラクタとプロパティを生成
  factory Usual({int? a}) = _Usual;
}

3.0 からできる書き方 (通常のコンストラクタ)


class Usual with _$Usual {
  // 開発者がコンストラクタと final プロパティを定義
  Usual({this.a});
  final int? a;
}

これにはいくつかのメリットがあって、

  • シンプルなデータクラスであれば、(「Freeezed しぐさ」な) factory を使わずに通常のコンストラクタでプロパティを定義できます。
  • 直和型(Union)はこれまでどおり factory を利用して定義できます。

また、この mixed mode により、通常のコンストラクタが利用可能になったことで、以下のような super コンストラクタの呼び出しも可能になりました。

class Base {
  Base(String value);
}


class Usual extends Base with _$Usual {
  // 通常のコンストラクタで super を呼び出す
  Usual({int? a}) : a = a ?? 0, super('value');
  final int a;
}

Inheritance and non-constant default values (継承と非定数デフォルト値)

従来の Freezed ではクラスの継承 (extends) や、コンストラクタ引数に対する非定数 (const でない) のデフォルト値の設定に制限がありました。
Freezed 3.0 では、mixed mode とプライベートコンストラクタ (クラス名._()) を組み合わせることで、これらの機能が利用可能になりました。

従来、Freezed クラスに独自のメソッドやプロパティを定義するには、以下のように空のプライベートコンストラクタ クラス名._(); を定義する必要がありました。
しかし、このプライベートコンストラクタ ._() にパラメータを渡すことはできませんでした。


class Example with _$Example {
  Example._(); // helloWorld() が動作するために必要

  factory Example(String name) = _Example;

  void helloWorld() => print('Hello $name');
}

Freezed 3.0 では、このプライベートコンストラクタに任意のパラメータを渡すことができます。Freezed はパラメータ名に基づいて、他の factory コンストラクタから値を受け渡します。これにより、super の呼び出しや非定数デフォルト値の設定が可能になります。

継承 (extends) の例:

class Subclass {
  Subclass.name(this.value);
  final int value;
}


class MyFreezedClass extends Subclass with _$MyFreezedClass {
  // プライベートコンストラクタ ._ でパラメータを受け取り、
  // super.name() を呼び出して基底クラス (Subclass) のコンストラクタに値を渡す
  MyFreezedClass._(super.value): super.name();

  // Freezed は value フィールドを MyFreezedClass._ に渡すようにコード生成する
  factory MyFreezedClass(int value, /* other fields */) = _MyFreezedClass;
}

以下のように、定数でないデフォルト値も利用できます。


sealed class Response<T> with _$Response<T> {
  // プライベートコンストラクタ ._ 内で、time パラメータに非定数のデフォルト値 DateTime.now() を設定
  Response._({DateTime? time}) : time = time ?? DateTime.now();

  // 各 factory コンストラクタは、必要に応じてプライベートコンストラクタ ._() に値を渡せる
  // time を指定しない場合、._() 内のデフォルト値が使われる
  factory Response.data(T value, {DateTime? time}) = ResponseData;

  // プライベートコンストラクタ ._() のパラメータが名前付きでオプショナルな場合 ({DateTime? time})、
  // factory コンストラクタで明示的に指定しない限り、._() 内で設定されたデフォルト値が使用される
  factory Response.error(Object error) = ResponseError;

  
  final DateTime time;
}

"Eject" union cases (特定の Union ケースの分離)

直和型 (Union types) の特定のケースについて、Freezed によるコード生成を行わず、独自に定義したクラスを使用できるようになりました。


sealed class Result<T> with _$Result {
  Result._();

  // ResultData は未定義なので、従来通り Freezed が自動生成します
  factory Result.data(T data) = ResultData;

  // 右辺に既存のクラス名 (ResultError) を指定すると、Freezed は
  // そのケース (ResultError) のコード生成を行わず、指定されたクラスをそのまま利用します
  factory Result.error(Object error) = ResultError;
}

// 独自に定義した ResultError クラス
// sealed クラスである Result<T> を extends する必要があります
class ResultError<T> extends Result<T> {
  ResultError(this.error): super._();
  final Object error;
}

この独自定義クラス (ResultError) も Freezed クラスにすることができ、前述の mixed mode と組み合わせることができます。

// mixed mode を用いたシンプルなクラス

class ResultError<T> extends Result<T> with _$ResultError<T> {
  ResultError(this.error): super._();
  final Object error;
}

// または、 factory を用いる書き方も可能

abstract class ResultError<T> extends Result<T> with _$ResultError<T> {
  ResultError._(): super._();
  factory ResultError(Object error) = _ResultError;
}

その他の変更

// dart format off の自動挿入

Freezed の設定でフォーマットが無効 (format: false、これがデフォルト) の場合、生成される .freezed.dart ファイルの先頭に // dart format off が付与されるようになりました。
これにより、生成されたファイルを CI のフォーマットチェックから除外する設定が不要になる場合があります。

Union ケースのプライベート化

直和型 (Union types) の各ケースに対応する factory コンストラクタをプライベート (_) にできるようになりました。


sealed class Result<T> with _$Result {
  // この ._data() は 3.0 から書けるようになった
  factory Result._data(T data) = ResultData; 
  factory Result.error(Object error) = ResultError;
}

参考資料

脚注
  1. だいたい公式の CHANGELOG / Migration Guide の翻訳です。一部ファイル生成時にエラーになるコードを修正しています。 ↩︎

GitHubで編集を提案

Discussion