[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 にパターンマッチングが導入されたことにより非推奨となっていた map
や when
などのメソッドが削除されました。
解決策
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 コンストラクタ)
class Usual with _$Usual {
// Freezed がコンストラクタとプロパティを生成
factory Usual({int? a}) = _Usual;
}
sealed
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;
}
以下のように、定数でないデフォルト値も利用できます。
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;
}
sealed
"Eject" union cases (特定の Union ケースの分離)
直和型 (Union types) の特定のケースについて、Freezed によるコード生成を行わず、独自に定義したクラスを使用できるようになりました。
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;
}
sealed
この独自定義クラス (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
コンストラクタをプライベート (_
) にできるようになりました。
class Result<T> with _$Result {
// この ._data() は 3.0 から書けるようになった
factory Result._data(T data) = ResultData;
factory Result.error(Object error) = ResultError;
}
sealed
参考資料
- 3.0 CHANGELOG: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/CHANGELOG.md#300---2025-02-25
- Migrate from v2 to v3: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/migration_guide.md
-
だいたい公式の CHANGELOG / Migration Guide の翻訳です。一部ファイル生成時にエラーになるコードを修正しています。 ↩︎
Discussion