### 私有命名參數
首先 Dart 3.12 新增了 Private named parameters,也就是私有命名參數,這個其實我們以前提過,就是在命名參數和私有欄位結合使用時,形式參數的初始化會有問題。
簡單來說,就是以前 Dart 的類別欄位是私有的,命名參數又不能以下底線開頭,所以不能直接寫:
dart 體驗AI代碼助手 代碼解讀複製代碼class User {
final String _name;
final int _age;
// Dart 3.11 之前:不允許這樣寫
User({required this._name, required this._age});
}
所以正常來說需要加一層公開參數,再用 initializer list 賦值給私有欄位:
dart 體驗AI代碼助手 代碼解讀複製代碼class User {
final String _name;
final int _age;
User({
required String name,
required int age,
}) : _name = name,
_age = age;
}
現在 Dart 3.12 開始可以直接寫:
dart 體驗AI代碼助手 代碼解讀複製代碼class User {
final String _name;
final int _age;
User({
required this._name,
required this._age,
});
@override
String toString() => 'User(name: $_name, age: $_age)';
}
void main() {
final user = User(name: 'Asher', age: 18);
print(user);
}
現在就是,欄位還是 _name、_age,但呼叫端參數名不是 _name、_age,而是自動去掉底線後的 name、age。
官方文件也明確說,
this._x這種 private named parameter 會讓呼叫者使用公開名稱x。
這個改動很小,但還是很實用,尤其是 Flutter/Dart 裡常見的不可變 model、DTO、設定類,以前經常因為私有欄位需要手寫 initializer list,現在可以直接保留私有欄位,同時又讓建構 API 是公開、乾淨的命名參數:
dart 體驗AI代碼助手 代碼解讀複製代碼class ApiConfig {
final String _baseUrl;
final Duration _timeout;
final Map<String, String> _headers;
const ApiConfig({
required this._baseUrl,
this._timeout = const Duration(seconds: 15),
this._headers = const {},
});
Uri endpoint(String path) => Uri.parse('$_baseUrl/$path');
Map<String, String> get headers => Map.unmodifiable(_headers);
}
void main() {
const config = ApiConfig(
baseUrl: 'https://api.example.com',
timeout: Duration(seconds: 10),
headers: {'X-App': 'demo'},
);
print(config.endpoint('users'));
}
另外,新的 prefer_initializing_formals lint 會提示那些可以改成 private named parameters 的地方,可以透過 dart fix --code=prefer_initializing_formals 批次處理:
css 體驗AI代碼助手 代碼解讀複製代碼dart fix --dry-run --code=prefer_initializing_formals
dart fix --apply --code=prefer_initializing_formals
另外一個就是 Primary Constructors,雖然是實驗性的,但 2026 Dart 終於新增了實用的語法糖特性 Primary Constructors,簡單來說就是:它允許你在 class header 裡同時宣告欄位和建構函式,例如:
dart 體驗AI代碼助手 代碼解讀複製代碼// 傳統寫法:欄位宣告 + 建構函式 各寫一遍
class Point {
int x;
int y;
Point(this.x, this.y);
}
dart 體驗AI代碼助手 代碼解讀複製代碼// Primary Constructors 寫法一行搞定
class Point(var int x, var int y);
怎麼樣?其實就是一個很簡單的語法糖,還有點老套的,但 Dart 也是終於吃上了,這次核心特性主要有:
dart 體驗AI代碼助手 代碼解讀複製代碼// final 欄位:不可變
class Person(final String name, final int age);
// var 欄位:可變
class Counter(var int count);
// 混合:只有 x 成為欄位,factor 只是普通參數
class Scaled(final int x, double factor) {
final double scaled = x * factor; // factor 在初始化作用域內可用
}
另外還有「空類別」可以用 ; 代替 {}:
dart 體驗AI代碼助手 代碼解讀複製代碼// 這兩種寫法完全等價
class A(final String name);
class A(final String name) {}
而如果是之前的 const,那麼就是如下命名建構函式 + const 的寫法:
dart 體驗AI代碼助手 代碼解讀複製代碼// 傳統寫法
class Point {
final int x;
final int y;
const Point._(this.x, this.y);
}
// Primary Constructors 函式寫法:const 放在類別名前
class const Point._(final int x, final int y);
另外還有可選參數和命名參數,在 Primary Constructors 下會變成如下寫法:
dart 體驗AI代碼助手 代碼解讀複製代碼// 帶預設值的可選位置參數
class Point(var int x, [var int y = 0]);
// 命名參數(型別可從預設值推斷)
class Config(var String host, {required var int port, var bool ssl = false});
繼承和 super 的寫法也有些變化:
dart 體驗AI代碼助手 代碼解讀複製代碼// 傳統寫法
class A {
final int a;
A(this.a);
}
class B extends A {
B(super.a);
}
// Primary Constructors 函式寫法
class A(final int a);
class B(super.a) extends A;
另外當需要初始化列表或函式體時,可以在 class 內用 this 宣告:
dart 體驗AI代碼助手 代碼解讀複製代碼// 傳統寫法
class DeltaPoint {
final int x;
final int y;
DeltaPoint(this.x, int delta) : y = x + delta;
}
// Primary Constructors 函式寫法 —— 使用 body part
class DeltaPoint(final int x, int delta) {
final int y;
this : y = x + delta; // 初始化列表放在這裡
}
// 更簡潔:利用「主初始化作用域」直接在欄位宣告處初始化
class DeltaPoint(final int x, int delta) {
final int y = x + delta; // delta 在這個作用域內可見!
}
這裡需要注意:Primary Constructors 參數在 class 內非
late欄位的初始化運算式中是可見的,這是普通建構函式做不到的。
最後就是斷言,新的場景結合 this 宣告:
dart 體驗AI代碼助手 代碼解讀複製代碼// 傳統寫法
class Point {
int x, y;
Point(this.x, this.y) : assert(0 <= x && x <= y * y);
}
// Primary Constructors 函式寫法
class Point(var int x, var int y) {
this : assert(0 <= x && x <= y * y);
}
其實這個 this 還有點意思,因為現在 Primary Constructors 現在引入了 declaring parameter(用 var/final 標記),那 this.xxx 這種舊語法在 Primary Constructors 函式裡還能用嗎?
實際上 this.xxx 在所有建構函式中的語意還是一樣的,實際上規範裡宣告參數 var T p 在語意上等價於把它替換成 this.p,並在 class 內新增欄位宣告 T p; 或 final T p;。
所以這兩種寫法在 Primary Constructors 函式中都是合法且語意明確的:
dart 體驗AI代碼助手 代碼解讀複製代碼// 用 var/final(宣告參數)—— 同時宣告欄位
class A(final int x, var String name);
// 用 this.field(欄位形式參數)—— 欄位已在類別體中宣告,建構函式只賦值
class B(this.x) {
late int x; // 欄位單獨宣告(例如需要 late 修飾符時)
external double d;
}
那麼這個語法好處就很明顯了,比如資料類別:
dart 體驗AI代碼助手 代碼解讀複製代碼// 傳統寫法(11 行)
class User {
final String id;
final String name;
final String email;
final int age;
User({required this.id, required this.name, required this.email, required this.age});
}
// Primary Constructors 函式(1 行)
class User({required final String id, required final String name, required final String email, required final int age});
帶計算欄位的類別(利用主初始化作用域):
dart 體驗AI代碼助手 代碼解讀複製代碼class Rectangle(final double width, final double height) {
final double area = width * height; // 直接用建構參數!
final double perimeter = 2 * (width + height);
}
甚至 extension type:
dart 體驗AI代碼助手 代碼解讀複製代碼extension type const Meters(double value);
extension type const UserId(int id);
// 使用
final m = Meters(3.14);
final uid = UserId(42);
跟典型的例子是:
dart 體驗AI代碼助手 代碼解讀複製代碼// 傳統寫法
class E extends A {
LongTypeExpression x1;
LongTypeExpression x2;
LongTypeExpression x3;
LongTypeExpression x4;
LongTypeExpression x5;
LongTypeExpression x6;
LongTypeExpression x7;
LongTypeExpression x8;
late int y;
int z;
final List<String> w;
E({
required this.x1, required this.x2, required this.x3, required this.x4,
required this.x5, required this.x6, required this.x7, required this.x8,
required this.y,
}) : z = y + 1,
w = const <Never>[],
super('Something') {
// 建構函式體...
}
}
dart 體驗AI代碼助手 代碼解讀複製代碼// Primary Constructors 函式寫法
class E({
required var LongTypeExpression x1,
required var LongTypeExpression x2,
required var LongTypeExpression x3,
required var LongTypeExpression x4,
required var LongTypeExpression x5,
required var LongTypeExpression x6,
required var LongTypeExpression x7,
required var LongTypeExpression x8,
required this.y, // y 需要 late,單獨宣告
}) extends A {
late int y;
int z = y + 1; // 直接寫在欄位宣告處!
final List<String> w = const <Never>[];
this : super('Something') {
// 建構函式體...
}
}
可以看到,雖然這個語法並不複雜,但是整體實用性還是挺不錯的,只是來得有點晚,最後總結可見:
特性說明宣告參數 var/final同時宣告欄位 + 建構函式參數,核心新語法this.field 相容在Primary Constructors 函式中與輔助建構函式語意完全相同; 空類別體語法糖,等價於 {}const 位置寫在類別名前:class const Foo(...)Body Part this:在類別體內補充初始化列表/函式體主初始化作用域參數在非 late 欄位初始化運算式中可見限制不能有其他非重定向生成式建構函式"Convert to a declaring parameter"已在 analysis_server 中實作,可批次重構整個檔案### Git Large File Storage
另外一個就是終於支援 Git Large File Storage (LFS),這個提了好多年,終於終於適配了,從 Dart 3.12 開始, dart pub 原生支援使用 Git 大型檔案儲存(LFS)的 Git 相依:
yaml 體驗AI代碼助手 代碼解讀複製代碼dependencies:
kittens:
git: https://github.com/munificent/kittens.git
這樣,對於直接在 Git 倉庫裡用對大型資產、資料模型或二進位檔案進行版本控制的團隊來說,現在拉相依可以快很多,管理也更合理,之前接 Unity lib 的時候,每次更新和拉取的痛苦啊,等了這麼久,終於來了 LFS。
最後一個有趣的就是 Agentic Hot Reload,透過使用 Dart MCP 伺服器,現在 AI 可以 Hot Reload,在背景 Dart Tooling Daemon 會自動透過 MCP 伺服器執行的 CLI 命令公開連線詳細資訊。
也就是,你的 AI 知道應該 Hot Reload 哪個專案,可以直接找到正在除錯的專案進行修改後和 Hot Reload。
這也是 Dart 近期比較有趣的更新了,整體來說雖然中規中矩,但是還是可以的,配合 Flutter 3.44,也算是一個整體的大更新。