目录

前言

一、什么是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. 所…