目錄
前言
一、什么是Key
1.StatelessWidget
2.StatefulWidget
3.加入Key后的效果
二、什么時候應該使用 Key?
1.Flutter判斷widget的邏輯
1.Flutter判斷組件身份的規則
1.Widget的類型(runtimeType)相同
2. Key相同(key == key)
2.舉例說明
3.Flutter在源碼中的行為(簡化版)
2.Flutter中key的使用場景
三、Key的幾種類型
1.ValueKey
2.ObjectKey
3.UniqueKey
4.GlobalKey
四、使用建議
前言
????????在 Flutter 中,Key是一個非常核心但常常被初學者忽略的概念。理解 Key的工作機制,能夠幫助你更深入地掌握 widget 的構建、重建與復用,尤其在構建動態列表、動畫組件、狀態保持等場景中尤為重要。
????????本文將通過兩個典型示例(StatelessWidget與StatefulWidget),配合代碼與動圖講解,讓你真正理解:
????????什么是 Key?
????????它是怎么影響組件狀態的?
????????什么時候該用?
????????用哪種 Key 更合適?
一、什么是Key
Key是widget的唯一標識符,用于告訴 Flutter:“這個 widget 在 widget tree 中是誰”。
????????在 Flutter 的渲染流程中,UI 是基于 widget tree 描述的。當你調用 setState() 或 UI 發生變化時,Flutter 會生成新的 widget tree,并與舊的 widget tree 進行比較(diff)來決定:
????????哪些 widget 可以復用
????????哪些 widget 需要銷毀并重新創建
????????這個“比對”的核心依據,就是:runtimeType + Key。
如果兩個 widget 的類型一樣并且 Key 一樣,則認為是“同一個” widget,Flutter 會復用它原有的 element 和 state
1.StatelessWidget
????????我們在頁面上加載兩個無狀態的 Card 組件,點擊浮動按鈕時,交換它們的順序,并使用 Provider 更新點擊次數。
? ? ? ?圖1.無狀態的widget
class CardWidget extends StatelessWidget {final String title;final Color color;final int count;final Function onClick;const CardWidget({super.key,required this.title,required this.color,required this.onClick,required this.count,});@overrideWidget build(BuildContext context) {return Card(color: color,child: ListTile(title: Text(title),subtitle: Text('我是 $title,點擊次數:$count'),trailing: IconButton(icon: const Icon(Icons.add),onPressed: () => onClick(),),),);}
}
? ? ? ? 我們在 State 中用一個數組保存組件,并在點擊按鈕時交換順序:
titles.insert(1, titles.removeAt(0)); // 交換兩個 widget
? ? ? ? 運行后,我們會發現:
????????組件交換成功
? ? ? ? 點擊次數保持正確
????????UI 沒有異常
????????為什么?因為 StatelessWidget 沒有內部狀態,重建不會有狀態丟失問題,Provider 中保存的狀態仍然正確。
2.StatefulWidget
? ? ? ? 還是以上面的UI效果為例:我們使用StatefuleWidget來實現一下,這里代碼就簡單很多了:
Widget代碼如下:
class StfCardWidget extends StatefulWidget {final String title;final Color color;const StfCardWidget({super.key, required this.title, required this.color});@overrideState<StfCardWidget> createState() => _StfCardWidgetState();
}class _StfCardWidgetState extends State<StfCardWidget> {int counter = 0;@overrideWidget build(BuildContext context) {return Card(color: widget.color,child: ListTile(title: Text(widget.title),subtitle: Text('我是 ${widget.title},點擊次數:$counter'),trailing: IconButton(icon: const Icon(Icons.add),onPressed: () => setState(() => counter++),),),);}
}
? ? ? 調用上述Widget的實例代碼如下:
class NoKeyDemo extends StatefulWidget {final String title;const NoKeyDemo({super.key, required this.title});@overrideState<NoKeyDemo> createState() => _NoKeyDemoState();
}class _NoKeyDemoState extends State<NoKeyDemo> {late List<Widget> titles;@overridevoid initState() {super.initState();titles = [StfCardWidget(title: 'CardA', color: Colors.red,),StfCardWidget(title: 'CardB', color: Colors.green,),];}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(widget.title)),body: Column(children:titles,),floatingActionButton: FloatingActionButton(onPressed: () {setState(() {titles.insert(1, titles.removeAt(0));});},child: const Icon(Icons.swap_vert),),);}
}
? ? ? ? 這個時候,我們會發現UI顯示會出現異常:
圖2.StatefulWidget交換
? ? ? ? 交換順序后,你會看到:????????
????????? 組件顏色交換了
????????? 文本標題也交換了
?????????? 點擊次數卻沒有交換!
3.加入Key后的效果
????????我們只需要簡單地為每個 widget 添加唯一 Key 即可修復問題:
titles = [StfCardWidget(title: 'CardA', color: Colors.red,key: const ValueKey("CardA"),),StfCardWidget(title: 'CardB', color: Colors.green,key: const ValueKey("CardB"),), ];
????????現在,Flutter 會根據 Key 精確判斷哪個組件是 CardA、哪個是 CardB,并且能保留各自的狀態。
????????? UI 狀態正常
????????? 點擊次數準確
????????? 沒有錯誤重用
二、什么時候應該使用 Key?
1.Flutter判斷widget的邏輯
????????在 Flutter 中,判斷一個組件是“同一個組件”還是“新組件”的核心邏輯發生在 Widget → Element 的構建與更新階段。
????????Flutter 在執行 setState() 或重新構建 widget tree 時,會嘗試“復用”已有的元素結構(Element)。這個過程就需要判斷新舊 widget 是否表示相同的界面組件,而這個判斷過程依賴于以下幾個關鍵因素:
1.Flutter判斷組件身份的規則
????????Flutter 使用以下邏輯判斷組件是否“是同一個”:
1.Widget的類型(runtimeType)相同
????????Flutter 首先比較新舊 widget 的類型是否相同:
oldWidget.runtimeType == newWidget.runtimeType
????????如果類型不同,必定是不同組件,Flutter 會銷毀舊的 Element 并新建一個。
2. Key相同(key == key)
????????如果類型相同,Flutter 再比較 Key:
oldWidget.key == newWidget.key
????????如果Key相同:認為是“同一個組件”,復用對應的 Element 和 State;
????????如果Key不同或缺失:Flutte?默認按照在 widget 樹中的順序來“嘗試匹配”;
????????這在組件結構發生變化或排序變動時會導致狀態錯亂或內容錯位。
????????總結一句話:
Flutter會認為 “類型 + Key” 一致的 widget 是“同一個”,從而復用狀態;否則就會創建新的 widget / element / state。
2.舉例說明
? 正確復用(加了 Key)
Widget build(BuildContext context) {return Column(children: [MyWidget(key: ValueKey('A')),MyWidget(key: ValueKey('B')),],);
}
????????即使交換順序,只要 Key 相同,狀態不會錯亂。
????????? 狀態錯亂(沒加 Key)
Widget build(BuildContext context) {return Column(children: [MyWidget(), // 沒 keyMyWidget(), // 沒 key],);
}
????????交換順序后,Flutter 只能按位置匹配,無法準確判斷哪個 widget 是原來的哪個,從而導致 UI 狀態錯位。
3.Flutter在源碼中的行為(簡化版)
????????Flutter 在 Element.updateChild() 方法中大致是這樣判斷的(偽代碼):
if (oldWidget.runtimeType == newWidget.runtimeType &&oldWidget.key == newWidget.key) {// 是同一個 widget,調用 updateelement.update(newWidget);
} else {// 是新 widget,銷毀舊的 element,創建新的oldElement.deactivate();newElement = inflateWidget(newWidget);
}
????????你可以在 Flutter 的源碼 widgets/framework.dart 中找到這部分邏輯。
2.Flutter中key的使用場景
? ? ? ? 這里作者總結了一些key的使用場景。
使用場景 | 是否建議使用 Key | 推薦 Key 類型 | 說明 |
---|---|---|---|
動態列表(ListView.builder) | ? 必須 | ValueKey | 區分每個列表項的身份 |
可排序列表(ReorderableListView) | ? 必須 | ValueKey | 不加 Key 無法正確交換 |
增刪組件時狀態保持 | ? | ValueKey 或 ObjectKey | 比如添加刪除輸入框時保留內容 |
交替顯示兩個相同類型組件(條件渲染) | ? | ValueKey | 用于避免狀態重用錯誤 |
拖動動畫 / Hero 動畫 | ? | ValueKey | 保證動畫關聯組件正確匹配 |
強制組件重建(比如重載某個部分) | ? | UniqueKey | 每次都不同,不復用 |
跨樹保留狀態 | ? 謹慎使用 | GlobalKey | 用于 Tab、導航或表單場景,但性能開銷較大 |
靜態布局 / 簡單組件 | ? 不需要 | - | 比如純文本、無狀態布局組件 |
????????在開發的過程中只要你遇到組件狀態異常、數據錯位、動畫錯位等問題,先看是不是忘了設置 Key!
三、Key的幾種類型
1.ValueKey
????????最常用的Key類型,用于根據具體值判斷是否是同一組件。
? ? ? ? 它的使用場景如下:
? ? ? ? 1.列表項有唯一標識(如 ID)
????????2.拖拽排序時標識組件
? ? ? ? 3.條件切換相似組件時防止錯亂
? ? ? ? 示例代碼如下:
ListView(children: items.map((item) => ListTile(key: ValueKey(item.id),title: Text(item.title),)).toList(),
);
2.ObjectKey
????????根據對象引用來判斷是否為同一個組件。
? ? ? ? 它的使用場景如下:
????????1.數據模型實例(同值但不同引用)需要被唯一識別
????????2.對象不能簡單地用一個字段表示唯一性
? ? ? ? 示例代碼如下:
ObjectKey(userModel); // 只有當 userModel 是同一引用時才認為是同一組件
3.UniqueKey
????????每次都是新的,不可比較,用于強制刷新組件。
? ? ? ? 它的使用場景如下:
????????1.組件內部狀態不可復用時強制刷新
? ? ? ?2.臨時widget(如動畫組件、過渡組件)
? ? ? ? 示例代碼如下:
MyWidget(key: UniqueKey()); // 每次都會新建 State
4.GlobalKey
? ? ? ? 它的使用場景如下:
????????1.跨組件調用組件的方法(如 Form 的驗證、滾動)
????????2.狀態跨布局保留(如 Tab 頁、動畫合并)
?????????3.控制多個組件狀態(慎用)
? ? ? ? 示例代碼如下:
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();Form(key: _formKey,child: ...
);// 在其他地方調用
_formKey.currentState?.validate();
?? 警告:
-
GlobalKey 會強制整個 subtree rebuild,性能開銷大;
-
Flutter 官方建議謹慎使用,僅用于必要場景。
四、使用建議
? ? ? ? 在實際的開發過程中,我總結了一下建議:
????????? 大多數場景用 ValueKey 就夠了,如列表、條件渲染、動畫組件。
????????🚫 不要濫用 GlobalKey,它會導致性能問題,限制復用優化。
????????🔁 如果你發現組件狀態錯亂,第一件事就是檢查是否缺失 Key。
? ? ? ??