dart-use-primary-constructors

dart-use-primary-constructors

熱門

協助使用者在 Dart 中撰寫語義與語法皆正確的主建構子(Primary Constructors),並遷移或使用全新的建構子語法、空內文分號語法、內文初始化列表語法,以及簡短精簡建構子語法。

2792星標
164分支
更新於 2026/8/5
SKILL.md
唯讀
名稱
dart-use-primary-constructors
描述

協助使用者在 Dart 中撰寫語義與語法皆正確的主建構子(Primary Constructors),並遷移或使用全新的建構子語法、空內文分號語法、內文初始化列表語法,以及簡短精簡建構子語法。

Dart 主建構子與新建構子語法 Skill

當需要協助使用者運用 Dart 的 主建構子(Primary Constructors) 功能來撰寫、重構或除錯程式碼時,請使用此 Skill。

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. 概述

主建構子讓開發者能直接在類別標頭(class header)中宣告非重定向的生成建構子(non-redirecting generative constructor)以及一組實例變數。這能大幅減少樣板程式碼(boilerplate)並提升程式碼可讀性。

主要優勢

  • 將欄位宣告、參數宣告與初始化整合為單一宣告,稱為「宣告式參數宣告(declaring parameter declaration)」。
  • 支援在非 late 欄位初始化器中安全地引用建構子參數(主初始化器作用域 Primary Initializer Scope)。
  • 允許使用分號(;)精簡地表示空的宣告內文。
  • 引入用於類別內建構子的簡短精簡語法(abbreviated concise syntax)。

2. 語法參考

2.1 基本類別標頭語法

若要宣告主建構子,請將參數列表緊接在型別名稱(及可選的型別參數)之後:

// 宣告欄位 x 與 y,以及生成建構子 Point(this.x, this.y)
class Point(var int x, var int y);

// 宣告 final 欄位
class PointFinal(final int x, final int y);

2.2 宣告式參數、初始化參數與一般參數

主建構子的參數列表包含三種不同類型的參數:

  1. 宣告式參數(Declaring Parameters):帶有 varfinal 修飾詞(例如 final int x)。它們會隱式地在類別中建立對應的實例欄位。
  2. 初始化參數(Initializing Parameters):帶有 this.super. 前綴(例如 this.xsuper.x)。它們分別用於初始化既有的欄位或父類別建構子參數。
  3. 一般參數(Regular Parameters):宣告時不帶修飾詞(例如 int y)。它們不會成為欄位,僅在初始化期間可用(例如在欄位初始化器或類別內文中的 this : 初始化列表中)。
// `x` 是欄位也是參數,因為它帶有關鍵字 `final`。特別的是,我們可以在主建構子內文部分的初始化列表中使用名稱 `x`。'y' 僅為參數,因為它既沒有 `final` 也沒有 `var` 關鍵字,但 `y` 會透過 `this :` 初始化列表傳遞給父類別建構子。
class C(final int x, int y) extends Base {
  this : super(y);
}

宣告式參數與初始化參數是達成相同目標的兩種方式:宣告一個包含實例欄位並在建構子中進行設定的類別。一般參數的相異之處在於其數值不會自動路由(routed)至實例欄位。

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 擴充型別(Extension Types)

擴充型別必須使用主建構子。

  • 標頭中的單一參數即為表現欄位(representation field)。
  • 表現變數不能使用 var 修飾詞(使用 var 會觸發 representation_field_modifier 錯誤)。
  • 表現變數可選擇性地使用 final 修飾詞。若未撰寫 final,則會自動推導;也就是說,該參數是在宣告其是否顯式為 final

2.5 空內文分號簡寫(;

當類別(class)、mixin class、mixin、擴充(extension)或擴充型別(extension type)的內文為空時,可用分號(;)替換 {} 大括號:

class C(int x);
mixin class MC;
extension type ET(int x);
mixin M;
extension Ext on C;

2.6 主建構子的內文部分 (this ...)

如果主建構子需要斷言(assertions)或自訂欄位初始化,可以在類別內文中使用 this : 語法進行宣告:

class Point(var int x, var int y) {
  // 類別內文中的初始化列表
  this : assert(x >= 0), y = y * 2;
}

你也可以使用此語法撰寫建構子內文(this {...})。

2.7 簡短精簡建構子語法

對於在類別內文中宣告的建構子,可以省略類別名稱,改用 newfactory 關鍵字替代:

傳統語法 簡短精簡語法
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 主初始化器作用域(Primary Initializer Scope)

宣告主建構子時,其形式參數會被引進到主初始化器作用域中。此作用域適用於類別內文中的非 late 欄位初始化器,以及主建構子的初始化列表(在 this : 之後)。
這允許非 late 欄位在宣告時直接引用建構子參數:

class DeltaPoint(final int x, int delta) {
  // 此處作用域可存取 'x' 與 'delta'
  final int y = x + delta;
}

3.2 Late 實例變數限制

主初始化器作用域適用於 late 實例變數初始化器。

  • 由於 late 變數可能在建構完成後才進行求值,因此其初始化器無法安全地存取建構子參數。
  • 嘗試在 late 欄位初始化器中存取主建構子參數會導致編譯時期錯誤。

3.3 遮蔽(Shadowing)

在主初始化器作用域內,主建構子參數會遮蔽同名的類別成員(欄位):

  • 在非 late 初始化器中:int y = x 其中的 x 是指參數 x
  • late 初始化器中:late int y = x 其中的 x 是指欄位 x(若存在),因為此時參數 x 已超出作用域。

3.4 生成建構子限制

為了確保主建構子(及相關的初始化器作用域)總是會執行:

  • 宣告了主建構子的類別、mixin class 或 enum 不能宣告任何其他非重定向生成建構子(擴充型別除外)。
  • 在內文中宣告的所有其他生成建構子必須(直接或間接)重定向至主建構子。

3.5 參數變更錯誤(Parameter Mutation Errors)

主建構子參數在初始化階段是不可賦值的(non-assignable)。

  • 在欄位初始化器或 this : 初始化列表中對參數進行任何重新賦值(例如 p = valuep++)都會導致編譯時期錯誤。

3.6 重複初始化錯誤

將欄位初始化二次(例如:一次在欄位宣告/初始化器中,一次在 this : 初始化列表或作為初始化形式參數)會導致編譯時期錯誤。


4. 診斷與疑難排解

大多數錯誤與 lint 規則都支援快速修復(quick-fix),執行 dart fix 即可修正這些違規。對於其他常見錯誤,請參考下表進行修復:

錯誤 / Lint 代碼 常見原因 解決方案
Invalid Late Access late 欄位初始化器中引用了主建構子參數。 將欄位改為非 late,或透過另一個非 late 欄位傳遞該值。
fieldInitializedInInitializerAndDeclaration 同時在宣告與 this : 列表中初始化變數。 移除其中一種初始化方式。
nonRedirectingGenerativeConstructorWithPrimary 在類別內文中宣告了非重定向生成建構子,且未重定向至主建構子。 修改內文中的建構子使其重定向(例如 this(...)),或直接移除該內文建構子。

5. 循序重構工作流程

工作流程 5.1:將類別遷移至主建構子

請按照以下步驟將冗長繁瑣的類別遷移至新建構子語法:

  1. 識別候選欄位與建構子
    找出生成建構子及其初始化的欄位。在此範例中為 nameage 欄位。

    // 遷移前
    class User {
      final String name;
      final int age;
      User(this.name, this.age);
    }
    
  2. 將欄位移動至標頭(Header)
    將欄位放置於標頭並加上 finalvar 修飾詞;若內文為空,請在末尾加上分號(;)。nameage 欄位現在於主建構子中分別改寫為宣告式參數 final String namefinal int age

    // 遷移後
    class User(final String name, final int age);
    
  3. 處理自訂初始化器與斷言(Assertions)
    若有初始化列表或 assert 區塊,請將其移動至內文中的 this 區塊:

    // 遷移前
    class Point {
      final int x;
      final int y;
      Point(this.x, this.y) : assert(x >= 0);
    }
    
    // 遷移後
    class Point(final int x, final int y) {
      this : assert(x >= 0);
    }
    
  4. 利用主初始化器作用域進行計算
    若欄位數值是根據參數計算得來,可在內文中宣告該欄位並直接使用參數賦值:

    // 遷移前
    class Rect {
      final double width;
      final double height;
      final double area;
      Rect(this.width, this.height) : area = width * height;
    }
    
    // 遷移後
    class Rect(final double width, final double height) {
      // 此處作用域可存取 'width' 與 'height'
      final double area = width * height;
    }
    
  5. 將類別內建構子改為重定向
    確保所有在內文中宣告的生成建構子都會重定向至主建構子:

    // 遷移前
    class Point {
      final int x;
      final int y;
      Point(this.x, this.y);
      Point.zero() : x = 0, y = 0;
    }
    
    // 遷移後
    class Point(final int x, final int y) {
      new zero() : this(0, 0); // 重定向至主建構子
    }
    

工作流程 5.2:套用簡短(精簡)內文建構子

當使用者希望保留類別內文中的建構子,但想降低冗長程度時,可建議使用簡短建構子語法:

// 遷移前
class DatabaseService {
  final String url;
  DatabaseService(this.url);
  DatabaseService.local() : url = 'localhost';
  factory DatabaseService.create() => DatabaseService('default');
}

// 遷移後
class DatabaseService {
  final String url;
  new(this.url); // 省略類別名稱,使用 'new'
  new local() : url = 'localhost'; // 具名建構子使用 'new local'
  factory create() => DatabaseService('default'); // 在 factory 中省略類別名稱
}