Dart 團隊在 Dart 3.12 的官方公告裡,正式把兩個語言級新功能交到開發者手上:primary constructors(主建構子)與 private named parameters(私有具名參數)。一句話總結:你以前要在 class body、參數列、初始化列表之間重複三次的欄位名稱與型別,現在可以濃縮成 class header 上的一行。primary constructors 仍標記為 experimental,private named parameters 則已穩定可用。對每天在寫 Flutter widget 的人來說,這是少數會直接改變你每天打字量的更新。
要懂這件事為什麼值得停下來看,得看 Dart 的歷史包袱。Dart 從 2.0 起就是一個安全但囉嗦的語言:宣告一個 immutable 的 model class,你得寫 final 欄位、再寫一個把每個欄位都 this.x 一遍的建構子,動輒十幾行樣板。社群為了逃避這件事,長年仰賴 freezed、built_value 這類 code generation 套件——但代價是 build_runner 的編譯時間、一堆 .g.dart 檔、以及新人看不懂的 macro 魔法。Dart 官方自己統計過:pub 套件裡超過五分之一(>20%)的欄位初始化,做的事情只是把一個和欄位同名、但前面少一個底線的參數,塞進私有欄位。primary constructors 就是要把這段最常見的樣板從語言層面消滅。
這對誰重要?所有寫 Flutter 的團隊——而 Flutter 在 2026 年仍是跨平台 App 的主力之一。少掉 build_runner,意味著大型專案的增量編譯能省下可觀時間;對接案工作室而言,model 層的開發速度直接影響報價競爭力。接下來拆解實際寫法、與 freezed 的取捨,以及一個多數教學不提的風險。
技術細節
primary constructors 讓你把參數直接宣告在 class header;private named parameters 則允許具名參數用底線開頭,編譯器自動幫你把它接到同名私有欄位,並對外去掉底線。
// Dart 3.11 以前:宣告一個 immutable model 要這麼多樣板
class Booking {
final String _id;
final DateTime _startAt;
final int _seats;
Booking({
required String id,
required DateTime startAt,
required int seats,
}) : _id = id,
_startAt = startAt,
_seats = seats;
}
// Dart 3.12:primary constructor + private named parameters
class Booking({
required String _id,
required DateTime _startAt,
required int _seats,
}) {
// 對外仍是 Booking(id: ..., startAt: ..., seats: ...)
}
在 analysis_options.yaml 開啟 experimental 旗標:
analyzer:
enable-experiment:
- primary-constructors
關鍵行為差異:對呼叫端來說,Booking(id: x, startAt: y, seats: z) 的 API 完全不變——底線只是內部慣例,編譯器在對外簽章自動脫掉。這是它和單純把參數設 public 最大的不同:你既保有封裝,又省掉初始化列表。
三類讀者的立刻行動
- 工程師(程式碼級):升級 Dart SDK 到 3.12,先在新的 model class 試 private named parameters(已穩定),primary constructors 則開 experiment 旗標在一個小模組試水溫。先別整庫改寫。
- 技術負責人(架構級):盤點
freezed/built_value依賴。單純 model 評估用 primary constructors 取代、砍掉 build_runner;但有copyWith、union types 需求的,freezed仍無可取代。 - 創業者/接案商(商業級):把「Dart 3.12 語法現代化 + 移除不必要的 code generation」做成小型技術債清理服務,交付週期短、效果可量化(編譯時間)。
比較與權衡
vs freezed:primary constructors 解的是樣板太多,但 freezed 解的是樣板 + copyWith + 相等比較 + union types + JSON 序列化一整包。只需要前者,就少一個依賴、少一輪 build_runner;重度依賴 copyWith 與 sealed union 的,freezed 暫時動不了。遷移成本:新專案幾乎零成本;舊專案建議只在新增 class 用新語法,不要為了統一風格大改既有 model。
不會告訴你的事
- primary constructors 仍是 experimental。寫進 production,未來版本語法若微調得跟著改;對要長期維護的客戶專案是賭注。
- 可讀性是雙面刃。class header 塞進一長串參數,一行裡藏五個欄位的型別與預設值,code review 時反而比攤開的初始化列表難逐項檢查。
- 底線自動脫殼的規則對新人有學習曲線,
_id對外變id不看文件不直覺。
未來 3 個月會發生什麼
預期 primary constructors 逐步走向 stable,IDE 重構工具會跟上「把舊建構子一鍵轉成 primary constructor」。freezed 作者可能調整定位,主打 union 與序列化。待觀察指標:Flutter 官方 sample、starter template 何時改用新語法當預設。
我的觀點
主流會說 primary constructors 終於來了、Dart 變簡潔了。我的判斷不同:真正的贏家不是打字變少,而是對 code generation 的依賴開始鬆動。過去 Flutter 生態被 build_runner 綁架。primary constructors 不會一夕殺死 freezed,但它讓「不裝 code generation 也能寫出乾淨 model」第一次成為預設選項。對 ScriptWalker 的啟示:別急著全改寫,但在所有新接的 Flutter 案,從第一天就用 3.12 語法立規範——model 層更輕、編譯更快、交接更好讀。把「不背 build_runner 技術債」當成報價時的隱形競爭力。