freezed 3.0.0を試してみる
どう変わったのか?
freezed 3.0.0がリリースされた。変更点があったようだ。気になり試してみた。
Mr. Remiのポストを翻訳してみた。
Freezed 3.0 がリリースされました!🥳
これには
- デフォルト値が一定でない
- 継承
- 「通常のクラス 」のサポート
などがあります!
以下はデフォルト値が一定でない例です 👇。
早速Dartだけのプロジェクトを作って試してみるとしよう。
create dart pj:
dart create freezed_three
add freezed:
dart pub add \
freezed_annotation \
--dev build_runner \
freezed \
json_annotation \
--dev json_serializable
watch:
dart pub run build_runner watch --delete-conflicting-outputs
シンブルなプロパティが1個の書き方
公式の書き方だが罠があって、?つけないとエラー出ました。null許容が必要だったようだ。
?なし
?あり
このコードを実行してみる。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'usual.freezed.dart';
sealed class Usual with _$Usual {
factory Usual({int? a}) = _Usual;
}
void main() {
final usual = Usual(a: 1);
print(usual);
print('main.g.dartなしで実行');
}
これだけのコードで使えるようだ。
But also:
でも、それだけじゃない:
の箇所を試すがこれはダメだった。
class Usual with _$Usual {
Usual({this.a});
final int a;
}
error
引数を必須にするコードに修正。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'usual.freezed.dart';
class Usual with _$Usual {
Usual({required this.a});
final int a;
}
void main() {
final usual = Usual(a: 1);
print(usual);
print('requiredを追加して実行');
}
しかしLintの警告が気になる。修正してみる。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'usual.freezed.dart';
class Usual with _$Usual {
Usual({required this.a});
// @overrideを追加する
final int a;
}
void main() {
final usual = Usual(a: 1);
print(usual);
print('@overrideを追加して実行');
}
This has multiple benefits:
Simple classes don't need Freezed's "weird" syntax and can stay simple
- Unions can keep using the usual factory syntax
- This also offers a way to use all constructor features, such as initializers or super():
これには複数の利点がある:
シンプルなクラスはFreezedの 「奇妙な 」構文を必要とせず、シンプルなままでいられる。
ユニオンは通常のファクトリ構文を使い続けることができる。
また、イニシャライザーやsuper()のようなコンストラクタの機能をすべて使うことができます:
class Base {
Base(String value);
}
class Usual extends Base with _$Usual {
Usual({int? a}) a = a ?? 0, super('value');
final int a;
}
これもコツが必要だった。Baseクラスにプロパティがないので、superを使っても親クラスのプロパティを参照できなかった。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'usual.freezed.dart';
class Base {
Base(this.value);
// プロパティの追加が必要だった。
final String value;
}
abstract class Usual extends Base with _$Usual {
factory Usual({(0) int a}) = _Usual;
// 継承したBaseクラスのプロパティを参照する
Usual._() : super('Base class valueを参照');
}
void main() {
final usual = Usual(a: 42);
print(usual.a);
// String valueを呼ぶ
print(usual.value);
}
New: Inheritance and non-constant default values.
When using Freezed, a common problem has always been the lack of extends support and non-constant default values.
Besides through "Mixed mode" mentioned above, Freezed now offers a way for factory constructors to specify non-constant defaults and a super(), by relying on the MyClass._() constructor:
It also has another benefit:
Complex Unions now have a way to use Inheritance and non-constant default values, by relying on a non-factory MyClass._() constructor.
For context:
Before, when a Freezed class specified a
新機能:継承と一定でないデフォルト値。
Freezed を使用する際によくある問題は、extends がサポートされていないことと、デフォルト値が一定でないことでした。
前述の 「混合モード」 に加えて、Freezed では MyClass._() コンストラクタに依存することで、ファクトリーコンストラクタで定数でないデフォルト値と super() を指定できるようになりました:
また、別の利点もあります:
複雑なユニオンは、ファクトリーではない MyClass._() コンストラクタに依存することで、継承と定数でないデフォルト値を使用できるようになりました。
コンテキストのために:
以前は、凍結されたクラスが
class Example with _$Example {
// Necessary for helloWorld() to work
Example._();
factory Example(String name) = _Example
void helloWorld() => print('Hello $name');
}
このコードも罠があった。abstract
と;
が必要だった???
import 'package:freezed_annotation/freezed_annotation.dart';
part 'example.freezed.dart';
// 抽象クラスを作成する
abstract class Example with _$Example {
// Necessary for helloWorld() to work
Example._();
factory Example(String name) = _Example; // ;が必要だった
void helloWorld() => print('Hello $name');
}
void main() {
final example = Example('abstractが必要');
// voidメソッドを呼ぶ
example.helloWorld();
}
However, that Example._() constructor was required to have no parameter.
Starting Freezed 3.0, this constructor can accept any parameter. Freezed will them pass it values from other factory constructors, based on name.
In short, this enables extending any class:
しかし、そのExample._()コンストラクタはパラメータを持たないことが要求されていた。
Freezed 3.0から、このコンストラクタは任意のパラメータを受け取ることができるようになりました。Freezedは他のファクトリーのコンストラクタから、名前に基づいて値を渡します。
つまり、これによって任意のクラスを拡張できるようになります:
class Subclass {
Subclass.name(this.value);
final int value;
}
class MyFreezedClass extends Subclass with _$MyFreezedClass {
// We can receive parameters in this constructor, which we can use with `super.field`
MyFreezedClass._(super.value): super.name();
factory MyFreezedClass(int value, /* other fields */) = _MyFreezedClass;
}
これはそのまま使えそう。解説の通りか。
Freezed 3.0から、このコンストラクタは任意のパラメータを受け取ることができるようになりました。Freezedは他のファクトリーのコンストラクタから、名前に基づいて値を渡します。
親クラスのパラメーターを受け取っているようだ。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'example.freezed.dart';
class Subclass {
Subclass.name(this.value);
final int value;
}
class MyFreezedClass extends Subclass with _$MyFreezedClass {
// このコンストラクタでパラメータを受け取ることができます。
MyFreezedClass._(super.value) : super.name();
factory MyFreezedClass(int value /* その他のフィールド */) = _MyFreezedClass;
}
void main() {
final myFreezedClass = MyFreezedClass(42);
print(myFreezedClass.value);
print('親クラスからコンストラクタにパラメーターを渡した');
}
It also enables non-constant default values:
また、デフォルト値を一定にしないことも可能である:
公式のサンプルはそのまま使えるようだ。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'example.freezed.dart';
sealed class Response<T> with _$Response<T> {
// We give "time" parameters a non-constant default
Response._({DateTime? time}) : time = time ?? DateTime.now();
// Constructors may enable passing parameters to ._();
factory Response.data(T value, {DateTime? time}) = ResponseData;
// If ._ parameters are named and optional, factory constructors are not required to specify it
factory Response.error(Object error) = ResponseError;
final DateTime time;
}
void main() {
final response = Response.data(42);
print(response.time);
print('<T>型のデータを渡した');
}
New: "Eject" union cases
Along with the mixed mode, it is also possible to eject a "union" case, by having it point to a custom class.
Concretely, you can do:
新機能:「ユニオン 」ケースの取り出し
ミックス・モードと同様に、カスタム・クラスを指すようにすることで、「ユニオン」ケースをイジェクトすることも可能です。
具体的には
when
が使えないようだ?
直和構造を表現するときに、Dart3.0からはsealed classを使用して長いけどDataTypeを定義したクラスを書けば良いのだが、昔からあるfreezedの方法を使いつつswitchで分岐処理を使うことができるようだ。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'example.freezed.dart';
sealed class Result<T> with _$Result {
Result._();
// Data does not exist, so Freezed will generate it as usual
factory Result.data(T data) = ResultData;
// We wrote a ResultError class in the same library, so Freezed won't do anything
factory Result.error(Object error) = ResultError;
}
// We manually wrote `ResultError`
class ResultError<T> extends Result<T> {
ResultError(this.error) : super._();
final Object error;
}
void main() {
// switch文で型をチェックする
final result = Result.data(42);
switch (result) {
case ResultData(:final data):
print(data);
case ResultError(:final error):
print('エラーが発生しました$error');
}
// case errorの型を指定する
final caseError = Result.error(Error());
switch (caseError) {
case ResultData(:final data):
print(data);
case ResultError(:final error):
print('エラーが発生しました$error');
}
}
This combines nicely with "Mixed mode" mentioned previously, as extracted union cases can also be Freezed classes:
これは、先に述べた「ミックス・モード」とうまく組み合わされる。というのも、抽出されたユニオンのケースもフリーズ・クラスになり得るからだ:
class ResultError<T>が2個あるのでどちらかをコメントアウトして使う。でもこの後試したら、 うまくいかなかった?
もう少し研究が必要そう。
// Using freezed with a simple class:
class ResultError<T> extends Result<T> {
ResultError(this.error): super._();
final Object error;
}
// Or using a factory:
class ResultError<T> extends Result<T> {
ResultError._(): super._();
factory ResultError(Object error) = _ResultError;
}
This feature offers fine-grained control over every parts of your models.
Note: Unfortunately, it is kind of required to "extend" the parent class (so here extends Result<T>). This is because Dart doesn't support sealed mixin class, so you can't do with Result<T> instead.
この機能は、モデルのあらゆる部分をきめ細かく制御することができる。
注:残念ながら、親クラスを 「extends 」する必要があります(ここでは extends Result<T>)。これは、Dartがsealed mixinクラスをサポートしていないためで、Result<T>で代用することはできません。
Other changes:
- Breaking: Removed map/when and variants. These have been discouraged since - Dart got pattern matching.
Breaking: Freezed classes should now either be abstract, sealed, or manually implements _$MyClass. - When formatting is disabled (default), Freezed now generates // dart format off. This prevents having to exclude generated file from formatting check in the CI.
- It is now possible to have a private constructor for unions
壊れる: map/whenとvariantsを削除。Dartがパターンマッチを導入して以来、これらは推奨されていない。
Breaking: Freezed クラスは、抽象か、sealed か、手動で _$MyClass を実装する必要があります。
フォーマットが無効な場合(デフォルト)、Freezed は // dart format off を生成するようになりました。これにより、CIで生成されたファイルをフォーマット・チェックから除外する必要がなくなりました。
ユニオンのプライベート・コンストラクタを持つことができるようになりました。
class Result<T> with _$Result {
// It wasn't possible to write _data before, but now is.
factory Result._data(T data) = ResultData;
factory Result.error(Object error) = ResultError;
}
sealed
factory Result._data(T data) = ResultData;
ここをプライベートにしてるとコンパイラの警告がでた。無くした方が良さそう。
これなら動く。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'example.freezed.dart';
sealed class Result<T> with _$Result {
factory Result.data(T data) = ResultData;
factory Result.error(Object error) = ResultError;
}
void main() {
final result = Result.data(42);
switch (result) {
case ResultData(:final data):
print(data);
case ResultError(:final error):
print('エラーが発生しました$error');
}
}
最後に
以前と違ってシンプルに書けるようになったり、スーパークラスを継承してプロパティの参照をfreezedクラスからできるようになったようだ。まだどれほどの影響があるのかわからないが新しいものを作るときに使ってみようと思う。
Discussion