Widget 子類重新宣告 key — 遮蔽父類屬性與 duplicate key 風險
事件
測試用的 TestRiveAnimation extends StatelessWidget 裡宣告了 final Key? key;,constructor 中透過 super(key: key) 傳給父類。Dart analyzer 警告 key overrides an inherited member。
加了 @override 可以消除警告,但問題沒有解決——class 裡現在有兩個 key slot(子類自己的和 Widget 繼承的),而 build 方法裡又寫了 Container(key: key),把同一個 key 同時掛在 parent widget 和 child widget 上。
根因
Widget 的 key 是 final 屬性,由 constructor 的 super(key:) 設定。子類重新宣告同名欄位會產生 shadowing:
- 子類的程式碼(包括
build)讀到的是子類自己的那份key - 父類
Widget的框架程式碼讀到的是父類的那份key
兩份值相同(因為 constructor 都有寫入),但語意上是兩個獨立的 slot。更危險的是,如果在 build 裡把這個 key 往下傳、讓同一層的多個 sibling 拿到相同的 Key 值,Flutter 在 diff 時會拋 duplicate key 錯誤。要精確一點:duplicate key 的觸發條件是同層 siblings 帶相同 key,或同一個 GlobalKey 同時掛在多處;parent 跟它的 child 帶相同 LocalKey 一般不會觸發(key 的唯一性是在 siblings 之間比對的)。重新宣告 key 的真正風險,是這個被遮蔽的欄位讓你以為傳下去的是繼承來的那份、實際散播出去造成 sibling 撞 key。
修法
不要重新宣告 key,改用 super.key:
1const TestRiveAnimation.asset(
2 this.asset, {
3 super.key, // 直接傳給 Widget,不產生新 slot
4 this.useArtboardSize = false,
5});build 裡也不要把 widget 自身的 key 再傳給 child——key 是給 framework 用來識別這個 widget 的,不該手動轉發。
判斷原則
在 Flutter 中,key、hashCode、runtimeType 這類從 Widget / Object 繼承的屬性,子類永遠不該用欄位覆蓋。如果需要自訂行為,覆寫 getter。