关于 dart-use-primary-constructors
面向 Dart 开发者的 Primary Constructors 参考技能,适用于编写、重构和调试相关代码。它说明版本要求、语法、作用域与诊断,并提供将现有类改写为该语法的流程。
主要功能
- 01说明版本要求与语法
- 02讲解参数和初始化作用域
- 03列出构造函数限制与错误
- 04将类重构为主构造函数
- 05使用简写的类内构造函数
适用场景
- 01改写字段和构造函数声明
- 02诊断 late 初始化器的作用域错误
- 03更新代码以使用 Primary Constructors
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dart-lang/ skills --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中启用:
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 声明参数、初始化参数和普通参数
主构造函数参数列表区分以下三种参数:
- 声明参数:由
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.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 主初始化器作用域
声明主构造函数时,其形式参数会引入主初始化器作用域。对于类体中的非 late 字段初始化器以及主构造函数的初始化列表(this : 之后),此作用域是当前作用域。
因此,非 late 字段可以在声明时直接引用构造函数参数:
class DeltaPoint(final int x, int delta) {
// 'x' and 'delta' are in scope here
final int y = x + delta;
}
3.2 Late 实例变量限制
主初始化器作用域不适用于 late 实例变量初始化器。
- 由于
late变量可能在构造完成后才求值,因此其初始化器无法安全地访问构造函数参数。 - 在
late字段初始化器中尝试访问主构造函数参数会导致编译时错误。
3.3 遮蔽
在主初始化器作用域内,主构造函数参数会遮蔽同名的类成员(字段):
- 在非
late初始化器中:int y = x引用参数x。 - 在
late初始化器中:late int y = x引用字段x(如果存在),因为参数x不在作用域内。
3.4 生成式构造函数限制
为确保主构造函数(及其关联的初始化器作用域)始终执行:
- 带有主构造函数的类、混入类或枚举声明不能声明其他非重定向的生成式构造函数(扩展类型除外)。
- 类体中声明的所有其他生成式构造函数必须直接或间接重定向到主构造函数。
3.5 参数修改错误
主构造函数参数在初始化阶段不可赋值。
- 在字段初始化器或
this :初始化列表中对参数进行任何赋值(例如p = value、p++)都会导致编译时错误。
3.6 重复初始化错误
重复初始化字段(例如,在字段声明/初始化器中初始化一次,又在 this : 初始化列表中初始化一次,或将其作为初始化形式参数)会导致编译时错误。
4. 诊断与故障排除
大多数错误和 lint 都有快速修复方案,请运行 dart fix 修复这些问题。对于其他常见错误,请参照下表进行修复:
| 错误 / Lint 代码 | 常见原因 | 解决方法 |
|---|---|---|
| 无效的 Late 访问 | 在 late 字段初始化器中引用主构造函数参数。 | 将字段改为非 late,或通过另一个非 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); -
处理自定义初始化器和断言: 如果有初始化列表或 assert 块,请将其移至主体中的
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
}