freezedは、データクラス、ユニオン(合併型)、およびクローニング用のコードジェネレーターです。
これはDartプログラミング言語におけるモデル定義を簡単にするために設計されています。
Note(注釈)
現在、Freezedのマクロを使用した初期プレビュー版が利用可能です。
詳細については、GitHubのリンク https://github.com/rrousselGit/freezed/tree/macros を参照してください。
本ブログの(上記https://pub.dev/packages/freezedの)翻訳および解説の目次頁(構成上のトップページ)は次のURLになります。
また前回記事は、こちらになります。
本頁は、上記(目次)のうちの、「How to use / Union types」の章の翻訳および解説頁になります。
2.5 Union types
他の言語から来た方は、「ユニオン型」、「シールされたクラス」、パターン マッチングなどの機能に慣れているかもしれません。
これらは型システムと組み合わせると強力なツールになりますが、Dart 2 ではサポートされていません。Dart 3 ではサポートされていますが、特に使いやすくはありません。
しかし、心配はいりません。Freezed はこれらをサポートしており、役立つユーティリティをいくつか生成します。簡単に言うと、Freezed クラスでは複数のコンストラクタを記述できます。
@freezed
sealed class Union with _$Union {
const factory Union.data(int value) = Data;
const factory Union.loading() = Loading;
const factory Union.error([String? message]) = Error;
}
NOTE)
この例では、Dart 3 で導入された seal キーワードを使用しています。Dart 3 を使用している場合は、Freezed ユニオンを定義するときに常に seal キーワードを使用する必要があります。まだ Dart 2 を使用している場合はこれを無視しても問題ありませんが、ネイティブ パターン マッチングのために Dart 3 にアップグレードすることをお勧めします。
これを行うことで、モデルは異なる相互排他的な状態になることができます。 特に、このスニペットはモデル Union を定義しており、そのモデルには 3 つの可能な状態があります。
- data
- loading
- error
定義したファクトリ コンストラクタの右手に意味のある名前をどの様に付けたかに注目して下さい。これらは後で役に立ちます。
また、この例では、次のようなコードを記述できなくなっていることに気づくかもしれません。
void main() {
Union union = Union.data(42);
print(union.value); // compilation error: property value does not exist
}
その理由については次のセクションで説明します。
(1) Shared properties
共有プロパティ
複数のコンストラクターを定義すると、すべてのコンストラクターに共通ではないプロパティを読み取ることができなくなります。 たとえば、次のように書くとします。
@freezed
sealed class Example with _$Example {
const factory Example.person(String name, int age) = Person;
const factory Example.city(String name, int population) = City;
}
そうなると、年齢と人口を直接読み取ることができなくなります。
var example = Example.person('Remi', 24);
print(example.age); //コンパイルエラー!
一方、すべてのコンストラクターで定義されているプロパティを読み取ることができます。たとえば、name 変数は Example.person と Example.city の両方のコンストラクターに共通です。 したがって、次のように書くことができます。
var example = Example.person('Remi', 24);
print(example.name); // Remi
example = Example.city('London', 8900000);
print(example.name); // London
同じロジックを copyWith にも適用できます。 すべてのコンストラクターで定義されたプロパティで copyWith を使用できます。
var example = Example.person('Remi', 24);
print(example.copyWith(name: 'Dash')); // Example.person(name: Dash, age: 24)
example = Example.city('London', 8900000);
print(example.copyWith(name: 'Paris')); // Example.city(name: Paris, population: 8900000)
一方、特定のコンストラクターに固有のプロパティは利用できません。
var example = Example.person('Remi', 24);
example.copyWith(age: 42); // compilation error, parameter `age` does not exist
この問題を解決するには、いわゆる「パターン マッチング」を使用してオブジェクトの状態をチェックする必要があります。
(2) Using pattern matching to read non-shared properties
パターンマッチングを使用して非共有プロパティを読み取る
このセクションでは、次の結合について考えてみましょう。
@freezed
sealed class Example with _$Example {
const factory Example.person(String name, int age) = Person;
const factory Example.city(String name, int population) = City;
}
パターン マッチングを使用して Example インスタンスのコンテンツを読み取る方法を見てみましょう。 このためには、スイッチを使用した Dart 3 の組み込みパターン マッチングを使用する必要があります。
switch (example) {
Person(:final name) => print('Person $name'),
City(:final population) => print('City ($population)'),
}
Dart 2を使用している場合は、Freezedによって生成された従来のパターンマッチングユーティリティを使用して、オブジェクトのコンテンツを検査することもできます。
(2-1) (Legacy) Pattern matching utilities
(レガシー) パターン マッチング ユーティリティ
警告)
Dart 3では、Dartにはシールクラスを使用したパターンマッチングが組み込まれています。そのため、パターンマッチングにFreezedの生成されたメソッドに頼る必要はなくなりました。
(2-1-1) When
when メソッドは、破壊を伴うパターン マッチングと同等です。
メソッドのプロトタイプは、定義されたコンストラクターによって異なります。
例えば、with の場合:
@freezed
sealed class Union with _$Union {
const factory Union(int value) = Data;
const factory Union.loading() = Loading;
const factory Union.error([String? message]) = ErrorDetails;
}
そして、when ではこうなります:
var union = Union(42);
print(
union.when(
(int value) => 'Data $value',
loading: () => 'loading',
error: (String? message) => 'Error: $message',
),
); // Data 42
一方、もしこの様に定義すると:
@freezed
sealed class Model with _$Model {
factory Model.first(String a) = First;
factory Model.second(int b, bool c) = Second;
}
when ではこうなります:
var model = Model.first('42');
print(
model.when(
first: (String a) => 'first $a',
second: (int b, bool c) => 'second $b $c'
),
); // first 42
各コールバックがコンストラクタの名前およびプロトタイプとどのように一致するかに注目してください。
(2-1-2) Map
map メソッドは when と同等ですが、構造化はありません。
次のクラスを考えてみましょう。
@freezed
sealed class Model with _$Model {
factory Model.first(String a) = First;
factory Model.second(int b, bool c) = Second;
}
この様なクラスの場合、when だとこうなります:
var model = Model.first('42');
print(
model.when(
first: (String a) => 'first $a',
second: (int b, bool c) => 'second $b $c'
),
); // first 42
map だとこうなります:
var model = Model.first('42');
print(
model.map(
first: (First value) => 'first ${value.a}',
second: (Second value) => 'second ${value.b} ${value.c}'
),
); // first 42
これは、たとえば copyWith/toString などの複雑な操作を実行する場合に便利です。
(続きの記事はこちら)