dart-use-primary-constructors について
Dart のプライマリコンストラクターの構文とリファクタリングを解説するガイドです。バージョン要件、引数のスコープ、制約、診断方法を扱います。
主な機能
- 01プライマリコンストラクターの宣言構文
- 02宣言・初期化・通常パラメーターの区別
- 03初期化スコープとフィールド規則の説明
- 04コンストラクターの制約とエラー
- 05移行と簡潔な構文の手順
活用シーン
- 01既存クラスをプライマリコンストラクターへ移行
- 02Dart バージョン別の機能対応確認
- 03コンパイルエラーの診断
- 04クラス内コンストラクター構文の短縮
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ flutter/ agent-plugins --skill dart-use-primary-constructorsインストール後、Claude Code で/dart-use-primary-constructorsを入力して読み込まれたか確認します。
詳細
Dart プライマリーコンストラクターと新しいコンストラクター構文のスキル
Dart の プライマリーコンストラクター機能を使用するコードの作成、リファクタリング、デバッグをユーザーが行う際に、このスキルを使用します。
Dart のバージョン要件
- Dart 3.13 以降:プライマリーコンストラクターはデフォルトで有効です。
- Dart 3.12:この機能は利用できますが、実験的な機能です。
--enable-experiment=primary-constructorsを使用するか、analysis_options.yamlで実験フラグprimary-constructorsを明示的に有効にする必要があります。
analyzer:
enable-experiment:
- primary-constructors
- Dart 3.11 以前:プライマリーコンストラクターはサポートされていません。
1. 概要
プライマリーコンストラクターを使うと、クラスヘッダーで非リダイレクトの生成コンストラクターと一連のインスタンス変数を直接宣言できます。これにより、定型コードを大幅に減らし、コードの可読性を高められます。
主な利点
- フィールド宣言、パラメーター宣言、初期化を、宣言パラメーター宣言と呼ばれる単一の宣言にまとめられます。
- 非
lateフィールド初期化子でコンストラクターパラメーターを安全に参照できます(プライマリー初期化子スコープ)。 - 空の宣言本体をセミコロン(
;)で簡潔に表現できます。 - 本体内コンストラクターに、省略形の簡潔な構文が導入されます。
2. 構文リファレンス
2.1 基本的なクラスヘッダー構文
プライマリーコンストラクターを宣言するには、型名(および省略可能な型パラメーター)の直後にパラメーターリストを置きます。
// Declares fields x and y, and a generative constructor Point(this.x, this.y)
class Point(var int x, var int y);
// Declares final fields
class PointFinal(final int x, final int y);
2.2 宣言パラメーター、初期化パラメーター、通常のパラメーター
プライマリーコンストラクターのパラメーターリストでは、次の3種類のパラメーターを区別します。
- 宣言パラメーター:
varまたはfinal修飾子で示します(例:final int x)。クラスに対応するインスタンスフィールドが暗黙的に作成されます。 - 初期化パラメーター:
this.またはsuper.接頭辞で示します(例:this.xまたはsuper.x)。それぞれ既存のフィールド、またはスーパークラスのコンストラクターパラメーターを初期化します。 - 通常のパラメーター:修飾子なしで宣言します(例:
int y)。フィールドにはならず、初期化中(フィールド初期化子や、クラス本体のthis :初期化子リストなど)にのみ使用できます。
// `x` is a field and a parameter because it has the keyword `final`. In particular, we can use the name `x` in the initializer list in the in-body part of the primary constructor. 'y' is a only parameter because it has neither of the keywords `final` or `var`, but `y` is passed to the super constructor via the `this :` initializer list.
class C(final int x, int y) extends Base {
this : super(y);
}
宣言パラメーターと初期化パラメーターは、コンストラクターで設定されるインスタンスフィールドを持つクラスを宣言するための、2つの方法です。通常のパラメーターは、値がインスタンスフィールドに自動的に渡されない点が異なります。
2.3 定数プライマリーコンストラクター
プライマリーコンストラクターを const にするには、宣言ヘッダーのクラス名または型名の前に const キーワードを置きます。
class const Point(final int x, final int y);
extension type const Ext(int x);
enum const MyEnum(final int x) {
entry(1);
}
2.4 拡張型
拡張型では、プライマリーコンストラクターを必ず使用する必要があります。
- ヘッダー内の単一パラメーターが表現フィールドになります。
- 表現変数に
var修飾子は使用できません(varを使用するとrepresentation_field_modifierエラーが発生します)。 - 表現変数には、省略可能な
final修飾子を指定できます。finalが指定されていない場合は推論されます。つまり、このパラメーターが明示的にfinalであるかどうかを宣言します。
2.5 空の本体のセミコロン省略形(;)
クラス、ミックスインクラス、ミックスイン、拡張、または拡張型の本体が空の場合、{} の代わりにセミコロン(;)を使用できます。
class C(int x);
mixin class MC;
extension type ET(int x);
mixin M;
extension Ext on C;
2.6 プライマリーコンストラクターの本体部分(this ...)
プライマリーコンストラクターでアサーションやカスタムフィールド初期化が必要な場合は、this : 構文を使って本体内に宣言できます。
class Point(var int x, var int y) {
// Initializer list in class body
this : assert(x >= 0), y = y * 2;
}
この構文(this {...})を使ってコンストラクター本体を記述することもできます。
2.7 省略形の簡潔なコンストラクター構文
クラス本体内で宣言されたコンストラクターでは、クラス名を省略し、new または factory キーワードに置き換えることができます。
| 従来の構文 | 省略形の簡潔な構文 |
|---|---|
MyClass() {} | new() {} |
MyClass.name() {} | new name() {} |
const MyClass(); | const new(); |
const MyClass.name(); | const new name(); |
factory MyClass() => ... | factory() => ... |
factory MyClass.name() => ... | factory name() => ... |
3. セマンティクスとスコープ規則
3.1 プライマリー初期化スコープ
プライマリーコンストラクターが宣言されると、その仮引数はプライマリー初期化スコープに導入されます。このスコープは、クラス本体の非遅延フィールド初期化子とプライマリーコンストラクターの初期化子リスト(this : の後)で現在のスコープとなります。
これにより、非遅延フィールドは宣言時にコンストラクターの仮引数を直接参照できます。
class DeltaPoint(final int x, int delta) {
// 'x' and 'delta' are in scope here
final int y = x + delta;
}
3.2 遅延インスタンス変数の制限
プライマリー初期化スコープは、late インスタンス変数の初期化子では有効になりません。
late変数は構築の完了後に評価される可能性があるため、初期化子からコンストラクターの仮引数に安全にアクセスすることはできません。lateフィールドの初期化子でプライマリーコンストラクターの仮引数にアクセスしようとすると、コンパイル時エラーになります。
3.3 シャドーイング
プライマリーコンストラクターの仮引数は、プライマリー初期化スコープ内で同名のクラスメンバー(フィールド)をシャドーイングします。
- 非遅延の初期化子では、
int y = xのxは仮引数を参照します。 late初期化子では、仮引数xはスコープ外のため、late int y = xのxはフィールド(存在する場合)を参照します。
3.4 生成コンストラクターの制限
プライマリーコンストラクター(および関連する初期化スコープ)が常に実行されるようにするには、次の制約があります。
- プライマリーコンストラクターを持つクラス、ミックスインクラス、または列挙型の宣言では、(拡張型を除き)リダイレクトしない生成コンストラクターをほかに宣言できません。
- 本体で宣言されるほかの生成コンストラクターは、すべて(直接または間接的に)プライマリーコンストラクターへリダイレクトする必要があります。
3.5 仮引数の変更エラー
プライマリーコンストラクターの仮引数は、初期化フェーズ内で代入できません。
- フィールド初期化子または
this :初期化子リスト内で仮引数に代入すると(例:p = value、p++)、コンパイル時エラーになります。
3.6 二重初期化エラー
フィールドを二度初期化すると(例:フィールド宣言/初期化子とthis : 初期化子リスト、または初期化形式のどちらでも初期化する場合)、コンパイル時エラーになります。
4. 診断とトラブルシューティング
ほとんどのエラーとリントにはクイックフィックスがあります。違反を修正するにはdart fixを実行してください。その他の一般的なエラーは、次の表を参考に修正してください。
| エラー/リントコード | 一般的な原因 | 解決方法 |
|---|---|---|
| Invalid Late Access | late フィールドの初期化子内でプライマリーコンストラクターの仮引数を参照している。 | フィールドを非遅延にするか、別の非遅延フィールドを介して値を渡します。 |
fieldInitializedInInitializerAndDeclaration | 変数を宣言時とthis : リストの両方で初期化している。 | いずれか一方の初期化を削除します。 |
nonRedirectingGenerativeConstructorWithPrimary | 本体内の生成コンストラクターが、プライマリーコンストラクターにリダイレクトせずに宣言されている。 | 本体内のコンストラクターをリダイレクトするように変更する(例:this(...))か、削除します。 |
5. 段階的なリファクタリングのワークフロー
ワークフロー 5.1:クラスをプライマリコンストラクターに移行する
冗長なクラスを新しいプライマリコンストラクターの構文に移行するには、次の手順に従います。
-
候補となるフィールドとコンストラクターを特定する: 生成コンストラクターと、それらが初期化するフィールドを探します。この場合は、
nameフィールドとageフィールドです。// Before class User { final String name; final int age; User(this.name, this.age); } -
フィールドをヘッダーに移動する: フィールドをヘッダーに配置し、
finalまたはvar修飾子を付けます。ボディが空の場合は、セミコロン(;)を追加します。nameフィールドとageフィールドは、それぞれ宣言パラメーターfinal String nameとfinal int ageとしてプライマリコンストラクターに記述されます。// After class User(final String name, final int age); -
カスタム初期化子とアサーションを処理する: 初期化子リストまたはアサートブロックがある場合は、ボディ内の
thisブロックに移動します。// Before class Point { final int x; final int y; Point(this.x, this.y) : assert(x >= 0); } // After class Point(final int x, final int y) { this : assert(x >= 0); } -
プライマリ初期化子のスコープを活用して計算する: パラメーターからフィールドの値を計算する場合は、ボディ内で宣言し、パラメーターを使って直接代入します。
// Before class Rect { final double width; final double height; final double area; Rect(this.width, this.height) : area = width * height; } // After class Rect(final double width, final double height) { // 'width' and 'height' are in scope here final double area = width * height; } -
ボディ内のコンストラクターをリダイレクトコンストラクターに変換する: ボディ内の生成コンストラクターがすべてプライマリコンストラクターにリダイレクトするようにします。
// Before class Point { final int x; final int y; Point(this.x, this.y); Point.zero() : x = 0, y = 0; } // After class Point(final int x, final int y) { new zero() : this(0, 0); // Redirects to primary }
ワークフロー 5.2:省略形(簡潔な)ボディ内コンストラクターを適用する
ユーザーがコンストラクターをクラスボディ内に残しつつ、冗長さを減らしたい場合は、省略形のコンストラクター構文を提案します。
// Before
class DatabaseService {
final String url;
DatabaseService(this.url);
DatabaseService.local() : url = 'localhost';
factory DatabaseService.create() => DatabaseService('default');
}
// After
class DatabaseService {
final String url;
new(this.url); // Omit class name, use 'new'
new local() : url = 'localhost'; // Use 'new local' for named constructors
factory create() => DatabaseService('default'); // Omit class name from factory
}