在 Flutter 中,Key
的主要作用是用于 标识 Widget,帮助框架在 widget 重建过程中更好地进行复用、比对和更新,从而提升性能并避免 UI 异常重建。
一、Key 的作用
在 Widget 重建时,Flutter 会尝试复用旧 Widget 树中的元素。如果没有 Key
,Flutter 只能通过 Widget 类型和位置来判断是否可以复用。如果 Widget 类型相同但内容不同,就可能出现 UI 错乱,例如 ListView
滚动后元素错位等问题。
引入 Key
后,Flutter 可以精准识别一个 Widget 的身份,即使它的位置发生变化,也能进行正确的复用或替换,避免不必要的重建。
二、Key 类型详解及最优使用场景
1. LocalKey
(抽象类)
- 所有局部键(
ValueKey
、ObjectKey
、UniqueKey
)的基类。 - 作用范围仅限于当前 widget subtree,不会跨 widget 树传递。
- 通常我们不直接使用它,但它是以下常见 key 的基础。
2. UniqueKey
- 每次创建时都唯一,即使内容相同也不会相等。
- 用于标识 完全独立的 widget 实例,防止复用。
✅ 推荐场景:
AnimatedList
、ReorderableListView
中动态添加的元素,每个 item 都应该是一个唯一实例。
dart
List<Widget> items = data.map((item) =>
MyItemWidget(
key: UniqueKey(),
data: item)
).toList();
⛔ 注意:
频繁使用 UniqueKey
会导致 widget 无法复用,增加重建成本。
3. ValueKey<T>(value)
- 用于通过一个简单的值(如字符串、数字)标识 widget。
- 当 value 相等时,Key 被认为是相同的。
✅ 推荐场景:
ListView.builder
渲染列表时,item 有稳定的 ID。
dart
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return MyItemWidget(key: ValueKey(item.id), data: item);
},
);
✅ 性能最优:因为 Flutter 能通过 value 快速查找旧元素并复用。
4. ObjectKey(Object value)
- 使用任意对象作为 key 的值,内部使用
==
和hashCode
比较。 - 常用于 model 本身作为标识。
✅ 推荐场景:
ListView
中 item 是复杂 model,且==
方法已重载。
dart
ListView(
children: models.map((model) => MyItemWidget(key: ObjectKey(model), data: model)).toList(),
);
5. GlobalKey
- 不仅用于识别 widget,还能访问其
State
、BuildContext
等。 - 适用于需要跨 widget 树访问的场景。
✅ 推荐场景:
- 表单校验(Form):
dart
final _formKey = GlobalKey<FormState>();
Form(key: _formKey, child: ...)
- 拿到某个 widget 的位置、尺寸等。
⛔ 注意:
慎用。每个 GlobalKey
都会注册在全局表中,有内存开销且会影响 diff 性能。
三、使用场景
下面我用实际 Flutter 示例分别演示 UniqueKey
、ValueKey
、ObjectKey
和 GlobalKey
的典型使用场景,并说明为什么这样使用是最合适的。
✅ 1. UniqueKey
示例:避免动画/排序时 widget 重用
dart
class UniqueKeyExample extends StatefulWidget {
@override
_UniqueKeyExampleState createState() => _UniqueKeyExampleState();
}
class _UniqueKeyExampleState extends State<UniqueKeyExample> {
List<Color> colors = [Colors.red, Colors.green, Colors.blue];
void _shuffle() {
setState(() {
colors.shuffle();
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: _shuffle, child: Text("Shuffle")),
Column(
children: colors
.map((color) => Container(
key: UniqueKey(), // 强制每次都当作新 widget
width: 100,
height: 50,
margin: EdgeInsets.all(5),
color: color,
))
.toList(),
)
],
);
}
}
📌 场景说明:
- 每次 shuffle 后,Flutter 因 key 不同,全部重新构建,适合动画或删除效果。
- 不适合频繁更新但数据稳定的列表(浪费性能)。
✅ 2. ValueKey
示例:按 id 精准复用列表项
dart
class ValueKeyExample extends StatefulWidget {
@override
_ValueKeyExampleState createState() => _ValueKeyExampleState();
}
class _ValueKeyExampleState extends State<ValueKeyExample> {
List<Map<String, dynamic>> users = [
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'},
{'id': 3, 'name': 'Charlie'},
];
void _reverseList() {
setState(() {
users = users.reversed.toList();
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: _reverseList, child: Text("Reverse")),
Column(
children: users
.map((user) => ListTile(
key: ValueKey(user['id']), // 稳定 id,避免错位
title: Text(user['name']),
))
.toList(),
),
],
);
}
}
📌 场景说明:
- 即使顺序变化,ID 不变时 Flutter 可以复用旧 widget,性能最佳。
- 非常适合列表、Grid、Sliver 等需要频繁重建但内容稳定的场景。
✅ 3. ObjectKey
示例:使用对象做 key,要求对象有适当的 ==
和 hashCode
dart
class Person {
final String name;
final int age;
Person(this.name, this.age);
@override
bool operator ==(Object other) =>
other is Person && name == other.name && age == other.age;
@override
int get hashCode => name.hashCode ^ age.hashCode;
}
class ObjectKeyExample extends StatelessWidget {
final List<Person> people = [
Person('Lily', 18),
Person('John', 20),
Person('Zoe', 22),
];
@override
Widget build(BuildContext context) {
return Column(
children: people
.map((person) => ListTile(
key: ObjectKey(person),
title: Text("${person.name} (${person.age})"),
))
.toList(),
);
}
}
📌 场景说明:
- 如果对象是业务主数据,可直接用对象作为 key,更自然。
==
必须重写,否则可能导致判断失败。
✅ 4. GlobalKey
示例:跨 Widget 获取状态或 BuildContext
dart
dart复制编辑class GlobalKeyExample extends StatelessWidget {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey, // 全局 Key 可访问 FormState
child: Column(
children: [
TextFormField(
validator: (value) => value == null || value.isEmpty ? 'Required' : null,
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Valid!")));
}
},
child: Text("Validate"),
)
],
),
);
}
}
📌 场景说明:
- 用于需要访问状态的组件(例如
FormState
、ScaffoldState
)。 - 尽量少用,避免性能问题和 widget 树混乱。
四、性能最优的理由总结
Key 类型 | 是否可复用 | 查找效率 | 使用开销 | 性能适用场景 |
---|---|---|---|---|
UniqueKey |
❌(不可复用) | ❌(唯一) | 高 | 插入/删除动画 |
ValueKey |
✅(可复用) | ✅(快速) | 低 | 稳定 ID 的列表 |
ObjectKey |
✅(可复用) | ⚠️(依赖==) | 中 | 对象有==重载 |
GlobalKey |
✅(全局) | ❌(慢) | 高 | 需要状态访问 |
所以为什么合理使用 key 能让性能最优?
- 减少 widget 重建 ------ Flutter 会 diff 前后 widget 树,key 能帮它"识别出自己"。
- 减少 render tree 重建 ------ 精准复用 element 和 render object,降低布局/绘制开销。
- 避免 UI 闪动或动画异常 ------ 保持 UI 状态稳定的一致性。