mcpField
D
dart-use-primary-constructors

dart-use-primary-constructors

github.com/flutter開発3.1k14.6k 回インストール2 日前に更新
リポジトリ

dart-use-primary-constructors について

Dart のプライマリコンストラクターの構文とリファクタリングを解説するガイドです。バージョン要件、引数のスコープ、制約、診断方法を扱います。

主な機能

  1. 01プライマリコンストラクターの宣言構文
  2. 02宣言・初期化・通常パラメーターの区別
  3. 03初期化スコープとフィールド規則の説明
  4. 04コンストラクターの制約とエラー
  5. 05移行と簡潔な構文の手順

活用シーン

  1. 01既存クラスをプライマリコンストラクターへ移行
  2. 02Dart バージョン別の機能対応確認
  3. 03コンパイルエラーの診断
  4. 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種類のパラメーターを区別します。

  1. 宣言パラメーター:var または final 修飾子で示します(例:final int x)。クラスに対応するインスタンスフィールドが暗黙的に作成されます。
  2. 初期化パラメーター:this. または super. 接頭辞で示します(例:this.x または super.x)。それぞれ既存のフィールド、またはスーパークラスのコンストラクターパラメーターを初期化します。
  3. 通常のパラメーター:修飾子なしで宣言します(例: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 Accesslate フィールドの初期化子内でプライマリーコンストラクターの仮引数を参照している。フィールドを非遅延にするか、別の非遅延フィールドを介して値を渡します。
fieldInitializedInInitializerAndDeclaration変数を宣言時とthis : リストの両方で初期化している。いずれか一方の初期化を削除します。
nonRedirectingGenerativeConstructorWithPrimary本体内の生成コンストラクターが、プライマリーコンストラクターにリダイレクトせずに宣言されている。本体内のコンストラクターをリダイレクトするように変更する(例:this(...))か、削除します。

5. 段階的なリファクタリングのワークフロー

ワークフロー 5.1:クラスをプライマリコンストラクターに移行する

冗長なクラスを新しいプライマリコンストラクターの構文に移行するには、次の手順に従います。

  1. 候補となるフィールドとコンストラクターを特定する: 生成コンストラクターと、それらが初期化するフィールドを探します。この場合は、name フィールドと age フィールドです。

    // Before
    class User {
      final String name;
      final int age;
      User(this.name, this.age);
    }
    
  2. フィールドをヘッダーに移動する: フィールドをヘッダーに配置し、final または var 修飾子を付けます。ボディが空の場合は、セミコロン(;)を追加します。name フィールドと age フィールドは、それぞれ宣言パラメーター final String name と final int age としてプライマリコンストラクターに記述されます。

    // After
    class User(final String name, final int age);
    
  3. カスタム初期化子とアサーションを処理する: 初期化子リストまたはアサートブロックがある場合は、ボディ内の 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);
    }
    
  4. プライマリ初期化子のスコープを活用して計算する: パラメーターからフィールドの値を計算する場合は、ボディ内で宣言し、パラメーターを使って直接代入します。

    // 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;
    }
    
  5. ボディ内のコンストラクターをリダイレクトコンストラクターに変換する: ボディ内の生成コンストラクターがすべてプライマリコンストラクターにリダイレクトするようにします。

    // 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
}

コメント