Flutter 几种 Key 的类型、使用场景及简单示例

126 阅读3分钟

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 类型,比如视图切换建议用 ValueKeyObjectKey,而动态列表可能需要 UniqueKey 来避免组件混淆。