dart-use-primary-constructors

dart-use-primary-constructors

热门

帮助用户在 Dart 中编写语法与语义正确的主构造函数(Primary Constructors),并迁移或使用全新的构造函数语法、空体分号语法、类体内初始化列表语法以及简写/精简构造函数语法。

2792Star
164Fork
更新于 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)以及一组实例变量。这极大减少了样板代码,显著提升了代码可读性。

核心优势

  • 将字段声明、参数声明与初始化合并为单一声明,称为“声明式参数声明”(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` 关键字。特别地,我们可以在主构造函数的类体内 `this :` 初始化列表中使用 `x` 这个名称。'y' 仅仅是一个参数,因为它既没有 `final` 也没有 `var` 关键字,但 `y` 通过 `this :` 初始化列表传递给了父类构造函数。
class C(final int x, int y) extends Base {
  this : super(y);
}

声明式参数和初始化参数是实现相同目标的两种方式:声明一个包含实例字段且字段值在构造函数中设置的类。普通参数的不同之处在于,它们的值不会自动路由并赋值给实例字段。

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

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 作用域遮蔽

主构造函数参数在主初始化器作用域内会遮蔽同名的类成员(字段):

  • 在非 late 初始化器中:int y = x 中的 x 指的是参数 x
  • late 初始化器中:late int y = x 中的 x 指的是字段 x(如果存在),因为此时参数 x 已超出作用域。

3.4 生成式构造函数限制

为保证主构造函数(及关联的初始化器作用域)总是会被执行:

  • 带有主构造函数的类、mixin class 或 enum 声明不能再声明任何其他非重定向生成式构造函数(extension types 除外)。
  • 类体内声明的所有其他生成式构造函数必须直接或间接重定向至主构造函数。

3.5 参数修改错误

主构造函数参数在初始化阶段是不可赋值(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. 将字段移至类头
    将字段放置在类头中,并加上 finalvar 修饰符;若类体为空则追加分号(;)。此时 nameage 字段在主构造函数中分别写作声明式参数 final String namefinal int age

    // 迁移后
    class User(final String name, final int age);
    
  3. 处理自定义初始化器与断言
    如果原构造函数包含初始化列表或 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 构造函数省略类名
}