Flutter key localKey globalKey 深入理解

本文详细探讨了Flutter中的Key概念,解释了为何在statelessWidget和statefulWidget中使用Key的原因。通过实例展示了Key在不同场景下的使用,如ValueKey、ObjectKey、UniqueKey和GlobalKey等,强调了Key在维护Widget状态和元素匹配中的重要作用。最后,总结了何时、何处以及如何选择合适的Key类型。

1. 什么是key

您可以使用key来控制框架将在widget重建时与哪些其他widget匹配。默认情况下,框架根据它们的runtimeType和它们的显示顺序来匹配。使用key时,框架要求两个widget具有相同的key和runtimeType。

  • Widget 更新机制

下面来来看Widget的源码。

@immutable
abstract class Widget extends DiagnosticableTree {
   
   
  const Widget({
   
    this.key });
  final Key key;
  ···
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
   
   
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

Widget 只是一个配置且无法修改,而 Element 才是真正被使用的对象,并可以修改。当新的 Widget 到来时将会调用 canUpdate 方法,来确定这个 Element是否需要更新。返回true时可以更新对element索引
canUpdate 对两个(新老) Widget 的 runtimeType 和 key 进行比较,从而判断出当前的 Element 是否需要更新

If the widgets have no key (their key is null), then they are considered a match if they have the same type, even if their children are completely different.

那么设么时候用key呢?

2. key的使用

实例
  • statelessWidget 实现两个颜色块交换
class StatelessContainer extends StatelessWidget {
   
   
  final Color randomCol = getRandomColor();
  @override
  Widget build(BuildContext context) {
   
   
    return Container(
      width: 100,
      height: 100,
      color: randomCol,
    );
  }
}

class MyHomePage extends StatefulWidget {
   
   
  MyHomePage({
   
   Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
   
   
List<Widget> widgets;

  //初始化加载资源
  @override
  void initState() {
   
   
    super.initState();
    widgets =  [
      StatelessContainer(),
      StatelessContainer()];
  }

  @override
  Widget build(BuildContext context) {
   
   
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: widgets,
        ),
      ),
      floatingActionButton: FloatingAct
<think>我们正在讨论的是在Flutter中使用Key参数可能导致不必要的重建的场景。根据引用[2]和引用[4]的内容,Key的主要作用是标识小部件,以便Flutter在重建小部件树时能够复用已有小部件,从而提高性能。然而,不正确的使用Key也可能导致不必要的重建。 以下是一些使用Key参数可能导致不必要重建的场景: 1. **在不需要唯一标识的Widget上使用Key**:如果Widget本身并不需要被唯一标识(例如,它是无状态的且不会在树中移动),那么为其添加Key(尤其是UniqueKey)会导致每次重建时都生成一个新的Key,从而强制Widget重建,即使其配置没有改变。例如: ```dart Widget build(BuildContext context) { return MyWidget(key: UniqueKey()); // 每次build都会生成新的UniqueKey,导致MyWidget重建 } ``` 2. **在列表中使用不稳定的Key**:当在列表(如ListView)中使用Key时,如果Key的值在每次构建时都改变(比如使用索引作为ValueKey,而列表项的顺序发生了改变),那么Flutter会认为旧的Widget无法复用,从而重建整个列表项。例如: ```dart ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile( key: ValueKey(index), // 当列表顺序改变时,相同的索引对应的Widget可能已经不同,导致重建 title: Text(items[index]), ); }, ) ``` 这种情况下,如果列表项的顺序发生变化(例如插入或删除项),则索引也会变化,导致Key变化,从而重建所有列表项。更好的做法是使用列表项的唯一标识(如id)作为Key。 3. **使用GlobalKey导致全局重建**:GlobalKey在整个应用中必须是唯一的,它们通常用于跨组件访问状态。但是,如果GlobalKey发生变化(比如被重新创建),那么所有引用该GlobalKey的Widget都会重建,并且相关联的状态也会丢失,导致整个子树重建。例如: ```dart class MyWidget extends StatefulWidget { @override _MyWidgetState createState() => _MyWidgetState(); } class _MyWidgetState extends State<MyWidget> { final GlobalKey _globalKey = GlobalKey(); // 每次状态重建都会重新生成GlobalKey @override Widget build(BuildContext context) { return AnotherWidget(key: _globalKey); } } ``` 在上面的例子中,每次_MyWidgetState重建(比如因为父组件的重建)都会生成一个新的GlobalKey,导致AnotherWidget及其子树重建。 4. **在父Widget重建时传递新的Key**:如果父Widget在重建时给子Widget传递了一个新的Key(即使子Widget的配置没有变化),那么子Widget会被视为新的Widget,导致其重建。例如: ```dart Widget build(BuildContext context) { // 每次父Widget重建,都会生成一个新的ValueKey(1),导致子Widget重建 return ChildWidget(key: ValueKey(1)); } ``` 如果这个Key是固定的(比如ValueKey(1)),但是父组件每次重建都重新创建这个Key对象(尽管值相同,但对象不同?实际上,对于相同类型的Key,如果其值相同,Flutter会认为它们是相等的,所以不会重建。但是,如果Key的类型或值改变,就会重建。因此,使用常量Key可以避免这个问题,例如将Key定义为常量:`static const key = ValueKey(1);` 然后在Widget中使用这个常量。 5. **错误地使用UniqueKey**:UniqueKey在每次构建时都会生成一个新的唯一值,因此它会导致Widget每次都重建。除非你确实希望每次构建都重建(比如在动画中强制重新创建),否则应该避免在不需要重建的地方使用UniqueKey。例如,在表单字段中,如果使用UniqueKey,那么每次重建都会重置表单字段的状态(因为重建了新的Widget),这通常不是想要的效果。 为了避免不必要的重建,我们应该: - 尽量使用稳定的Key(例如,使用数据模型中的唯一标识作为ValueKey)。 - 避免在不需要唯一标识的Widget上使用Key。 - 对于不会改变位置的Widget,避免使用Key。 - 使用const构造函数创建Widget,这样在父组件重建时,如果子组件的配置没有变化,Flutter就不会重建子组件。 根据引用[5]:在Column下的children里,同一级内的Key需要唯一,但不同级别之间即使Key相同也没有影响。因此,我们在设置Key时,只需要保证同一级(同一个父Widget下的直接子Widget)的Key唯一即可。 总结:Key的使用需要谨慎,不正确的使用会导致不必要的重建,影响性能。正确使用Key可以优化性能,避免重建问题[^1][^2][^4]。 ### 相关问题 1. 在Flutter中,如何选择合适的Key类型(如ValueKey, ObjectKey, UniqueKey)? 2. 如何避免在列表中使用Key导致的性能问题? 3. GlobalKey的使用场景和注意事项有哪些? 4. 在Flutter中,如何通过Key来保存和恢复状态? 5. 如何通过Flutter DevTools来检测由Key引起的重建问题?</think>### Flutter 中使用 Key 参数导致不必要重建的场景分析 在 Flutter 中,Key 虽然能优化 Widget 复用,但**错误使用 Key 反而会引发不必要的重建**,以下是常见问题场景及解决方案: --- #### 🚫 1. 动态生成 Key (最严重问题) **场景**:在 build 方法内生成 UniqueKey 或 ObjectKey ```dart Widget build(BuildContext context) { return MyWidget( key: UniqueKey(), // ❌ 每次 rebuild 都会生成新 Key child: ... ); } ``` **问题**: - 每次 build 都创建新 Key - Flutter 认为这是**全新 Widget**,强制重建整个子树 - 破坏 Widget 复用机制,导致性能下降[^2][^4] **解决方案**: ```dart // ✅ 在 State 初始化时创建 Key late final UniqueKey _widgetKey = UniqueKey(); Widget build(BuildContext context) { return MyWidget( key: _widgetKey, // Key 保持稳定 child: ... ); } ``` --- #### 🚫 2. 列表中使用不稳定的 ValueKey **场景**:使用列表索引作为 ValueKey ```dart ListView.builder( itemBuilder: (_, index) => ListItem( key: ValueKey(index), // ❌ 索引变化导致 Key 变化 data: items[index] ) ) ``` **问题**: - 当列表项**增删/排序**时,相同索引对应不同数据 - Flutter 误判为**新 Widget**,重建本可复用的项 - 引发列表滚动位置跳变[^4][^5] **解决方案**: ```dart // ✅ 使用数据唯一标识 ListView.builder( itemBuilder: (_, index) => ListItem( key: ValueKey(items[index].id), // 稳定标识 data: items[index] ) ) ``` --- #### 🚫 3. GlobalKey 的误用 **场景**:在频繁重建的 Widget 中使用 GlobalKey ```dart Widget build(BuildContext context) { return MyForm( key: GlobalKey(), // ❌ 每次 rebuild 创建新 GlobalKey ); } ``` **问题**: - GlobalKey **全局唯一性**要求高 - Key 变化导致关联的 Element 树**完全重建** - 丢失所有子 Widget 状态(如表单输入)[^3] **解决方案**: ```dart // ✅ 提升到 State 或更高层级 class _ParentState extends State<Parent> { final formKey = GlobalKey(); // 稳定存储 Widget build(BuildContext context) { return MyForm(key: formKey); } } ``` --- #### 🚫 4. Key 类型不匹配 **场景**:同一位置交替使用不同类型 Key ```dart Widget build(BuildContext context) { return condition ? WidgetA(key: ValueKey(1)) : WidgetB(key: ObjectKey(1)); // ❌ 类型不同 } ``` **问题**: - Flutter 通过 `runtimeType + Key` 判断 Widget 等同性 - Key 类型变化会被视为**完全不同的 Widget** - 触发不必要的销毁和重建[^3] **解决方案**: ```dart // ✅ 保持同类型 Key Widget build(BuildContext context) { return condition ? WidgetA(key: const ValueKey('A')) : WidgetB(key: const ValueKey('B')); } ``` --- #### 🚫 5. 同级 Key 冲突 **场景**:同一父节点下 Key 不唯一 ```dart Column( children: [ Child(key: ValueKey(1)), Child(key: ValueKey(1)), // ❌ 同级 Key 重复 ], ) ``` **问题**: - Flutter 要求**同级 Widget 的 Key 必须唯一** - Key 冲突导致框架无法正确复用 Widget - 触发异常或非预期重建[^5] **解决方案**: ```dart // ✅ 确保同级 Key 唯一 Column( children: [ Child(key: const ValueKey('child1')), Child(key: const ValueKey('child2')), ], ) ``` --- ### 🔧 调试建议 1. **开启 DevTools 重建跟踪**: ```bash flutter run --profile ``` 查看标记为黄色的高重建频率 Widget[^2] 2. **检查 Key 稳定性**: - 在 build 方法中避免 `UniqueKey()/GlobalKey()` 直接实例化 - 使用 `const` 修饰固定 Key(如 `const ValueKey('id')`) 3. **优先使用 LocalKey**: ```dart // ✅ 优于 GlobalKey 的方案 final ValueKey myKey = const ValueKey('stable'); ``` > **关键原则**:Key 应是**稳定且可预测的**标识符。当 Key 值或类型在 Widget 生命周期内变化时,必然导致不必要的重建[^1][^4]。 --- ### 📊 Key 使用决策表 | 场景 | 推荐 Key 类型 | 重建风险 | |-----------------------|---------------------|----------| | 列表项 | `ValueKey(item.id)` | 低 | | 表单状态保持 | `GlobalKey` (稳定存储) | 中 | | 动画/强制重建 | `UniqueKey` (谨慎使用) | 高 | | 无状态静态组件 | **不使用 Key** | 零 | --- ### 相关问题 1. 如何在 ListView 中使用 Key 避免滚动位置跳变? 2. GlobalKey 与 InheritedWidget 在状态传递上的性能差异? 3. Flutter DevTools 中如何检测由 Key 引起的重建问题? 4. 使用 Key 时如何避免丢失子 Widget 的状态? 5. 在什么场景下应该完全避免使用 Key
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值