🎉

freezedで自動生成されたファイルの内容の知りたい

に公開

はじめに

freezedパッケージによって自動生成されるコードについて調べていきます。個人的に実装上どのような挙動になることを想定しているのかを把握しておきたい。

※記事書き終わった感想としては抽象クラスまで深堀ると記事を書いていてきりがなかったので、解説に物足りない部分があるかもしれません。

freezed とは?

freezedは、Dart のイミュータブルなデータクラスを簡単に作成するためのコード生成パッケージです。手動で実装すると面倒な機能を自動的に提供してくれます。以前に freezed が必要な理由に関して記載した記事があるので、気になる場合はご覧ください。

https://zenn.dev/muranaka/articles/ce282d544da2e5

freezedを用いる場合、json_serializableも一緒に用いられる場合が多いので、以降は両方の解説していきます。

自動生成コードの構造

今回用いるコードはfreezedjson_serializableパッケージによって自動生成されたものです。

1. イミュータブルな性質

class _Person implements Person {
  const _Person(
      {required this.firstName, required this.lastName, required this.age});

  
  final String firstName;
  
  final String lastName;
  
  final int age;

  // ...
}

すべてのフィールドがfinalで宣言されており、一度初期化されると変更できません。これによりイミュータブル(不変)な性質を持ちます。

2. copyWith メソッド


(includeFromJson: false, includeToJson: false)
('vm:prefer-inline')
_$PersonCopyWith<_Person> get copyWith =>
    __$PersonCopyWithImpl<_Person>(this, _$identity);

オブジェクトの一部のフィールドだけを変更した新しいインスタンスを作成するためのメソッドです。例えば:

// firstName、lastName、ageを持つPersonオブジェクトがあるとき
final person = Person(firstName: '太郎', lastName: '山田', age: 30);

// 年齢だけを変更した新しいインスタンスを作成
final updatedPerson = person.copyWith(age: 31);

// 元のオブジェクトは変更されない
print(person.age); // 30
print(updatedPerson.age); // 31

3. 等価性比較(==演算子)


bool operator ==(Object other) {
  return identical(this, other) ||
      (other.runtimeType == runtimeType &&
          other is _Person &&
          (identical(other.firstName, firstName) ||
              other.firstName == firstName) &&
          (identical(other.lastName, lastName) ||
              other.lastName == lastName) &&
          (identical(other.age, age) || other.age == age));
}

構造的等価性を実装しており、同じフィールド値を持つ 2 つのインスタンスは等しいと判断されます。参照ではなく値に基づいた比較ができます。

構造的等価性とは?

2つのデータを比べたとき,「値として等しいこと」を構造的等価性(structural equality)という。「値として」だけでなく,メモリ上の同じ位置を占めていることを物理的等価性(physical equality)という。

https://blog.panicblanket.com/archives/192#:~:text=2つのデータを比べ,性(physical equality)という。

4. ハッシュコード生成

(includeFromJson: false, includeToJson: false)

int get hashCode => Object.hash(runtimeType, firstName, lastName, age);

等価性比較と連動して、オブジェクトの一意性を表すハッシュコードを生成します。同じ内容のオブジェクトは同じハッシュコードを持ちます。

runtimeType とは?

runtimeTypeは Dart のすべてのオブジェクトが持つプロパティで、そのオブジェクトの実際の型(クラス)を表します。

5. toString 実装


String toString() {
  return 'Person(firstName: $firstName, lastName: $lastName, age: $age)';
}

デバッグやログ出力時に便利な文字列表現を提供します。

6. JSON シリアライズ/デシリアライズ

factory _Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);


Map<String, dynamic> toJson() {
  return _$PersonToJson(
    this,
  );
}

JSON とオブジェクト間の変換機能を提供します。これはjson_serializableパッケージと連携して実装されています。

使用例

このようなコードが自動生成されるため必要なperson.dartファイルはこちらです。

// person.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:json_annotation/json_annotation.dart';

part 'person.freezed.dart';
part 'person.g.dart';


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

  factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}

このシンプルな記述から、先ほど見た複雑なコードが生成されます。便利。

まとめ

freezedパッケージによる自動生成コードによる恩恵は大きく、より安全で保守性の高いコードを書くことができます。

Discussion