> For the complete documentation index, see [llms.txt](https://ryukiedev.gitbook.io/wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryukiedev.gitbook.io/wiki/flutter/23.key.md).

# 23.Key

### Key 的作用

我们创建这样一个示例：

```
import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  //key的作用就非常大了！！
  List<Widget> items = [
    ColorItem('第1个'),
    ColorItem('第2个'),
    ColorItem('第3个'),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Key的作用'),
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: items,
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          setState(() {
            items.removeAt(0);
          });
        },
      ),
    );
  }
}

class ColorItem extends StatefulWidget {
  final String title;

  ColorItem(this.title, {Key? key}) : super(key: key);

  @override
  _ColorItemState createState() => _ColorItemState();
}

class _ColorItemState extends State<ColorItem> {
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      child: Text(widget.title),
      color: color,
    );
  }
}
```

![1](/files/3kj0mkYajQGLvW0mdMHE)

运行发现了上面的现象，作为有 `iOS` 开发经验的我们，很容易联想到这应该是和`复用`有关的现象。

#### 复用问题

这里我们需要理解， `ColorItem` 作为一个 `StatefulWidget` 它是由两部分组成的： `Widget` 和 `State` 。这里就是 `Widget` 被移除后， `State` 还在内存中。所以出现了复用异常的问题。

那么将数据放在 `Widget` 中按照这里分析的理论来说应该就没有这个现象了吧？我们试试：

```
class ColorItem extends StatefulWidget {
  final String title;
  // 移到 Widget 中
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);

  ColorItem(this.title, {Key? key}) : super(key: key);

  @override
  _ColorItemState createState() => _ColorItemState();
}

class _ColorItemState extends State<ColorItem> {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      child: Text(widget.title),
      color: widget.color,
    );
  }
}
```

![2](/files/lUNLHKe8DqfKAam5SzL9)

验证成功！那么具体原因是什么呢？

#### 增量渲染与 canUpdate

我们来到 `Widget` 的实现中， `canUpdate` 方法决定了一个 `Widget` 的 `Element` 是否会被更新，而 `Element` 的更新，又直接关系到了`增量渲染`。

同时满足两个条件：

* 新旧 `Widget` 的类型相同
* 新旧 `Widget` 的 `Key` 相同
  * 如果 `Key` 是空的，只通过类型判断，就算他们的子 `Widget` 完全不同

这里就很好的解释了一开始发生的异常现象。

```
/// Whether the `newWidget` can be used to update an [Element] that currently
/// has the `oldWidget` as its configuration.
///
/// An element that uses a given widget as its configuration can be updated to
/// use another widget as its configuration if, and only if, the two widgets
/// have [runtimeType] and [key] properties that are [operator==].
///
/// 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.
static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
}
```

**图解**

`Widget` 和 `Element` 是一一对应的，而 `State` 是在 `Element` 中的。

![4](/files/F6mKPIrHXbgEQB0B5fSS)

* 移除一个 `Widget` 开始检查
* 第一个 `Element` 检查到第二个 `Widget`
  * 调用 `canUpdate`
    * 类型相同
    * `Key` 为空
    * return `ture`
  * 于是就用 `第一个Element` 更新 `第二个Widget`
* 依次类推，就发生了前面的异常现象

![5](/files/4NC9Hx6INSRY6zfEUb2f)

既然这里提到了 `Key` ，那么加上 `Key` 是否就可以上面这个问题呢？

#### Key 的使用

这里为每个 `ColorItem` 加上 `Key` ， 并将 `Color` 属性放回 `State` 中：

```
...

class _HomePageState extends State<HomePage> {
  //key的作用就非常大了！！
  List<Widget> items = [
    ColorItem(
      '第1个',
      key: ValueKey(1),
    ),
    ColorItem(
      '第2个',
      key: ValueKey(2),
    ),
    ColorItem(
      '第3个',
      key: ValueKey(3),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    ...
  }
}

class ColorItem extends StatefulWidget {
  final String title;
  ...
}

class _ColorItemState extends State<ColorItem> {
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  ...
}
```

![3](/files/k4ufW25uaro8rxUTFtIt)

有效！

### Key

`Key` 本身是一个抽象类，有一个工厂方法：

```
/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
///
/// A new widget will only be used to update an existing element if its key is
/// the same as the key of the current widget associated with the element.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=kn0EOS-ZiIc}
///
/// Keys must be unique amongst the [Element]s with the same parent.
///
/// Subclasses of [Key] should either subclass [LocalKey] or [GlobalKey].
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
@immutable
abstract class Key {
  /// Construct a [ValueKey<String>] with the given [String].
  ///
  /// This is the simplest way to create keys.
  const factory Key(String value) = ValueKey<String>;

  /// Default constructor, used by subclasses.
  ///
  /// Useful so that subclasses can call us, because the [new Key] factory
  /// constructor shadows the implicit constructor.
  @protected
  const Key.empty();
}
```

* 它有两个子类
  * `LocalKey`
    * 同一个 `父Element` 内唯一的
  * `GlobalKey`
    * 整个 `App` 内唯一

> 刚才用的 `ValueKey` 是 `LocalKey` 的子类。

### LocalKey

区别哪个 `Element` 要保留，哪个 `Element` 要删除。

```
/// A key that is not a [GlobalKey].
///
/// Keys must be unique amongst the [Element]s with the same parent. By
/// contrast, [GlobalKey]s must be unique across the entire app.
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
abstract class LocalKey extends Key {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const LocalKey() : super.empty();
}
```

* `ValueKey`
  * 以值作为参数（数字、字符串等）
* `ObjectKey`
  * 以对象作为参数
* `UniqueKey`
  * 创建唯一标识

### GlobalKey

在整个 `App` 中 `唯一的Key`。 `GlobalKey` 可以唯一标识一个元素， 比如访问一个 `BuildContext` 或者一个 `Widget。对于` `StatefulWidget` 而言， `GlobalKey` 也可以访问 `State` 。

当有 `GlobalKey` 的 `Widget` 被移动到 `Widget树` 中新的位置的话，会重新渲染他们的子树。

为了渲染子树，一个 `Widget` 必须在同一个`动画帧`内完成在树中，从旧位置移动到新位置。

重新渲染一个使用 `GlobalKey` 的 `Element` 是很消耗性能的，因为会触发调用所有相关的 `State` 的 `deactivate` 方法，然后让所有依赖 `InheritedWidget` 的 `Widget` 重建。

所以如果你不需要达到上面的效果，那么建议使用其他的 `Key` 。

#### 注意点

* 两个在同一个树中的 `Widget` 不能同时有相同的 `GlobalKey` 。尝试这样做的话在运行时会触发异常。
* `GlobalKeys` 不应该再每次 `build` 的时候被重新创建。他们应该是长期被一个 State 所持有的。
  * 例如：
    * 每次 `build` 的时候都创建一个新的 `GlobalKey` 会丢弃和 `旧Key` 相关的子树，并为`新Key`创建一个新树。除了会损害性能，这种操作还有可能会对子树造成未知的影响。
    * 例如子树中的 `GestureDetector` 将无法继续追踪正在进行中的手势，因为它会在每次 `build` 的时候被重新创建
* 比较好的做法是：
  * 让一个 `State` 持有这个 `GlobalKey` ，并且在 `Build` 方法外初始化它
  * 比如在 `State.initState` 中

#### GlobalKey简单示例

```
import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: GlobalKeyDemo(),
    );
  }
}

class GlobalKeyDemo extends StatelessWidget {

  final GlobalKey<_GKeyItemState> _gKey = GlobalKey();

  GlobalKeyDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Key的作用'),
      ),
      body: Center(
        child: GKeyItem(key: _gKey,),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          _gKey.currentState?.setState(() {
            _gKey.currentState?.count += 1;
          });
        },
      ),
    );
  }
}


class GKeyItem extends StatefulWidget {
  const GKeyItem({Key? key}) : super(key: key);

  @override
  _GKeyItemState createState() => _GKeyItemState();
}

class _GKeyItemState extends State<GKeyItem> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$count'),
    );
  }
}
```
