談談Flutter中的Key

目錄

前言

一、什么是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。

? ? ? ??

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91910.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91910.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91910.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

重生之我在暑假學習微服務第八天《OpenFeign篇》

個人主頁&#xff1a;VON文章所屬專欄&#xff1a;微服務 微服務系列文章 重生之我在暑假學習微服務第一天《MybatisPlus-上篇》重生之我在暑假學習微服務第二天《MybatisPlus-下篇》重生之我在暑假學習微服務第三天《Docker-上篇》重生之我在暑假學習微服務第四天《Docker-下篇…

風光儲綜合能源系統雙層優化規劃設計【MATLAB模型實現】

本模型基于雙層優化框架&#xff0c;利用KKT條件、大M法、對偶理論求解&#xff0c;專注于綜合能源系統&#xff08;微電網&#xff09;多電源容量優化配置的模型介紹。代碼采用CPLEX求解器&#xff0c;注釋詳盡&#xff0c;非常適合新手學習該類問題的建模與求解思路。 模型總…

雪花算法重復id問題

原理解析 雪花算法實現簡單、適配性強&#xff0c;無論是電商訂單、日志追蹤還是分布式存儲&#xff0c;都能滿足 “唯一、有序、高效、可擴展” 的核心需求&#xff0c;因此成為分布式ID主流選擇。雪花算法生成的ID是一個64位的整數&#xff0c;由多段不同意義的數字拼接而成&…

MQTT 入門教程:三步從 Docker 部署到 Java 客戶端實現

在物聯網&#xff08;IoT&#xff09;與邊緣計算快速發展的今天&#xff0c;設備間的高效通信成為核心需求。MQTT 作為一種輕量級的發布 / 訂閱模式協議&#xff0c;憑借其低帶寬占用、強穩定性和靈活的消息路由能力&#xff0c;已成為物聯網通信的事實標準。無論是智能家居的設…

公網服務器上Nginx或者Openresty如何屏蔽IP直接掃描

0x01 背景云服務器很多時候為了通信需要設置公網訪問&#xff0c;但是網絡當中存在很多的掃描器&#xff0c;無時無刻在掃描&#xff0c;當80,443端口暴露時&#xff0c;成了這些掃描IP的攻擊對象&#xff0c;無時無刻收到威脅。0x02 掃描攻擊方式1.直接通過公網IP地址進行一些…

C語言(長期更新)第8講 函數遞歸

C語言&#xff08;長期更新&#xff09; 第8講:函數遞歸 跟著潼心走&#xff0c;輕松拿捏C語言&#xff0c;困惑通通走&#xff0c;一去不回頭~歡迎開始今天的學習內容&#xff0c;你的支持就是博主最大的動力。 目錄 C語言&#xff08;長期更新&#xff09; 第8講 函數遞歸…

[硬件電路-129]:模擬電路 - 繼電器的工作原理、關鍵指標、常用芯片與管腳定義

一、工作原理繼電器是一種基于電磁感應原理的自動開關裝置&#xff0c;通過控制小電流電路實現大電流電路的通斷。其核心結構包括&#xff1a;電磁鐵&#xff08;線圈鐵芯&#xff09;&#xff1a;通電時產生磁場&#xff0c;吸引銜鐵動作。觸點系統&#xff1a;包含常開觸點&a…

Haproxy調度算法 - 靜態算法介紹與使用

文章目錄一、概述二、socat工具三、static-rr四、firstHAProxy通過固定參數 balance 指明對后端服務器的調度算法&#xff0c;該參數可以配置在listen或backend選項中。HAProxy的調度算法分為靜態和動態調度算法&#xff0c;但是有些算法可以根據參數在靜態和動態算法中相互轉換…

模擬激光相機工作站版本6.0 5.2.32 6.0.44 6.031 5.2.20

模擬激光相機工作站版本6.0 5.2.32 6.0.44 6.031 5.2.20

AWS Blockchain Templates:快速部署企業級區塊鏈網絡的終極解決方案

無需精通底層架構&#xff0c;一鍵搭建Hyperledger Fabric或以太坊網絡&#xff01;AWS Blockchain Templates 可幫助您快速基于不同的區塊鏈框架在 AWS 上創建和部署區塊鏈網絡。區塊鏈是一種分布式數據庫技術&#xff0c;用于維護不斷增長的交易記錄和智能合約集合&#xff0…

Vue 服務端渲染 Nuxt 使用詳解

Nuxt 是基于 Vue 的高層框架&#xff0c;專注于服務器端渲染應用開發。它封裝了繁瑣的配置和通用模式&#xff0c;提供了開箱即用的 SSR 功能&#xff0c;使開發者能夠專注于編寫業務邏輯。 1. Nuxt 的核心特性 SSR 支持&#xff1a;默認支持服務端渲染&#xff0c;提高應用性…

使用ACK Serverless容器化部署大語言模型FastChat

核心概念 阿里云ACK Serverless&#xff1a;是一種基于 Kubernetes 的無服務器容器服務。用戶無需管理底層節點和服務器&#xff0c;即可快速部署容器化應用&#xff0c;并根據實際使用的 CPU 和內存資源按需付費&#xff0c;只專注于應用本身而非基礎設施管理。 FastChat&…

最新Android Studio漢化教程--兼容插件包

[ ] 軟件版本&#xff1a;Android Studio Meerkat Feature Drop | 2024.3.2 Build #AI-243.25659.59.2432.13423653, built on April 30, 2025 Runtime version: 21.0.613368085-b895.109 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. Toolkit: sun.awt.windows.WT…

Unity_數據持久化_IXmlSerializable接口

Unity數據持久化 三、XML數據持久化 3.5 IXmlSerializable接口 3.5.1 IXmlSerializable接口基礎概念 什么是IXmlSerializable接口&#xff1a; IXmlSerializable 是.NET框架提供的一個接口&#xff0c;允許類自定義XML序列化和反序列化的過程。當默認的XML序列化行為無法滿足需…

如何快速解決PDF解密新方法?

有時從網絡下載的PDF文檔會帶有加密限制&#xff0c;導致無法編輯、復制或打印。它的體積僅約10MB&#xff0c;無需安裝&#xff0c;解壓即用。遇到受限制的文件時&#xff0c;只需將其拖入界面&#xff0c;選擇是否覆蓋原文件&#xff0c;點擊執行&#xff0c;瞬間完成解密。「…

譯|數據驅動智慧供應鏈的構成要素與關聯思考

數據質量&#xff0c;通過識別關鍵決策和瓶頸構建信息供應鏈。該模型適用于優化庫存管理、自動化物流、預測需求、實現產品全生命周期追溯及應對突發風險。例如&#xff0c;通過AI機器人自動管理倉庫&#xff0c;或利用數字孿生模擬和優化全球采購網絡。 匯總來自三篇文章&…

OS21.【Linux】環境變量

目錄 1.與環境變量有關的實驗 A.對比命令和自制程序的運行 為什么.像ls、pwd這樣的命令運行是不需要加路徑? 執行自制程序而不加路徑的方法,看看PATH環境變量 方法1:將自制程序移動到系統的搜索路徑下 方法2:臨時修改PATH環境變量 B.查看系統中所有環境變量 解釋幾個常…

加密流量論文復現:《Detecting DNS over HTTPS based data exfiltration》(上)

本文將以我個人的理解去閱讀該篇流量加密論文&#xff0c;并在下一篇盡力對其中的實驗部分進行復現。話不多說&#xff0c;先從論文開始著手。 內容介紹 傳統的DNS(Domain Name System)協議是以明文傳輸的。DNS作為互聯網的基礎設施&#xff0c;最初設計時主要考慮的是功能和效…

Apache RocketMQ 中Message (消息)的核心概念

好的&#xff0c;我們來深入理解一下 Apache RocketMQ 中 Message (消息) 這個核心概念。這份文檔詳細闡述了消息的定義、在模型中的位置、內部屬性、約束和使用建議。 你可以將 Message 看作是 RocketMQ 系統中數據傳輸和處理的最小原子單位。它承載了業務數據&#xff0c;并附…

C 語言問題

1. C語言中 union 與 struct 的區別類型structunion內存分配機制編譯器為每個成員?獨立分配內存空間&#xff0c;總內存大小 所有成員大小之和&#xff08;考慮內存對齊&#xff09;所有成員?共享同一段內存空間&#xff0c;總內存大小 ?最大成員的大小?數據存儲特性1. 所…