1. ValueKey
ValueKey
用于基于某个值(例如字符串、数字等)来唯一标识一个组件。适合切换相似视图的场景,例如在 ListView
中区分列表项,或动态切换输入框。
使用场景
- 动态视图切换:比如切换不同的输入模式。
- 列表项区分:确保同类型的列表项不会混淆或丢失状态。
示例
动态切换视图
if (loginMode == LoginMode.pwd)
CustomInputField(
key: ValueKey('passwordInputField'), // 用唯一字符串标识
hintText: '请输入密码',
obscureText: true,
),
if (loginMode == LoginMode.code)
CustomInputField(
key: ValueKey('codeInputField'), // 用唯一字符串标识
hintText: '请输入验证码',
keyboardType: TextInputType.number,
),
列表项区分
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(items[index].id), // 使用唯一 ID 标识
title: Text(items[index].name),
);
},
);
2. UniqueKey
UniqueKey
总是生成一个完全唯一的 Key,即使传入相同的值,每次调用都不同。它适合在动态添加或删除组件时,确保组件在重新排列时强制销毁和重建。
使用场景
- 动态列表重建:例如重新排序的拖拽列表。
- 确保组件强制重建:每次都需要从头开始渲染时使用。
示例
强制销毁和重建组件
if (loginMode == LoginMode.pwd)
CustomInputField(
key: UniqueKey(), // 每次都会生成不同的 Key,确保重新渲染
hintText: '请输入密码',
obscureText: true,
),
if (loginMode == LoginMode.code)
CustomInputField(
key: UniqueKey(), // 每次都会生成不同的 Key
hintText: '请输入验证码',
keyboardType: TextInputType.number,
),
动态拖拽排序列表
ReorderableListView(
onReorder: (oldIndex, newIndex) {
setState(() {
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
children: items.map((item) {
return ListTile(
key: UniqueKey(), // 确保拖动后不会复用旧组件
title: Text(item.name),
);
}).toList(),
);
3. ObjectKey
ObjectKey
使用一个对象实例作为唯一标识。这在动态数据(如模型对象)对应组件时非常有用,适合复杂对象关联的场景。
使用场景
- 对象对应组件:当某个对象始终代表特定组件时。
- 模型对象区分:比如在某个 UI 列表中,直接用对象作为唯一标识。
示例
使用模型对象作为 Key
class Item {
final int id;
final String name;
Item(this.id, this.name);
}
List<Item> items = [
Item(1, 'Item 1'),
Item(2, 'Item 2'),
];
ListView(
children: items.map((item) {
return ListTile(
key: ObjectKey(item), // 使用对象作为 Key
title: Text(item.name),
);
}).toList(),
);
区分视图中的不同对象
CustomInputField(
key: ObjectKey(loginMode), // 以 loginMode 对象为 Key
hintText: loginMode == LoginMode.pwd ? '请输入密码' : '请输入验证码',
obscureText: loginMode == LoginMode.pwd,
);
4. GlobalKey
GlobalKey
是功能最强的 Key,可以跨组件访问状态,或者从父级调用组件的方法。它适合复杂交互或需要直接操作子组件的场景。
使用场景
- 跨组件访问状态:例如需要从父组件调用子组件的某些方法。
- 高级操作:比如表单验证或滚动控制。
示例
跨组件调用方法
final GlobalKey<FormFieldState> passwordFieldKey = GlobalKey<FormFieldState>();
CustomInputField(
key: passwordFieldKey,
hintText: '请输入密码',
);
// 在父组件中调用子组件方法
void clearPasswordField() {
passwordFieldKey.currentState?.reset();
}
表单验证
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
Form(
key: formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(hintText: '用户名'),
validator: (value) => value!.isEmpty ? '用户名不能为空' : null,
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
// 验证通过
}
},
child: Text('提交'),
),
],
),
);
总结:如何选择 Key?
Key 类型 | 使用场景 | 示例 |
---|---|---|
ValueKey | 动态切换视图或标识列表项 | 列表项的唯一 ID 或动态视图的唯一值:ValueKey('passwordInputField') |
UniqueKey | 确保强制销毁并重新创建组件 | 每次视图切换都重新渲染:UniqueKey() |
ObjectKey | 基于对象实例动态关联组件 | 用对象本身标识组件:ObjectKey(item) |
GlobalKey | 跨组件访问状态或需要直接调用子组件的方法 | 表单验证、滚动控制:GlobalKey<FormState>() |
根据实际需求选择合适的 Key 类型,比如视图切换建议用 ValueKey
或 ObjectKey
,而动态列表可能需要 UniqueKey
来避免组件混淆。