简介

key是widget、element和semanticsNode的惟一标识,同一个parent下的所有element的key不能反复,然而在特定条件下能够在不同parent下应用雷同的key,比方page1和page2都能够应用ValueKey(1)

罕用key的UML关系图如上,整体上key分为两大类-LocalKey和GlobalKey,这两个key都是抽象类,LocalKey的实现类有 ValueKey、ObjectKey和UniqueKey,GlobalKey实现类有LabeledGlobalKey和GlobalObjectKey。

Key

@immutableabstract class Key {  const factory Key(String value) = ValueKey<String>;  @protected  const Key.empty();}

Key是所有key的基类,外部实现了一个工厂构造函数,默认创立String类型的ValueKey。外部还是先了一个empty的构造函数,次要是给子类用的。

LocalKey

abstract class LocalKey extends Key {  const LocalKey() : super.empty();}

LocalKey没有理论作用,次要是用来辨别GlobalKey的,其具体的实现类有ValueKey、ObjectKey、UniqueKey。

ValueKey

class ValueKey<T> extends LocalKey {  const ValueKey(this.value);  final T value;  @override  bool operator ==(Object other) {    if (other.runtimeType != runtimeType)      return false;    return other is ValueKey<T>        && other.value == value;  }  @override  int get hashCode => hashValues(runtimeType, value);

外部保护了泛型类型的value属性,并实现了==和hashCode办法。只有两个ValueKey的value属性相等,那么就认为两个Key相等。

ObjectKey

class ObjectKey extends LocalKey {  const ObjectKey(this.value);  final Object? value;  @override  bool operator ==(Object other) {    if (other.runtimeType != runtimeType)      return false;    return other is ObjectKey        && identical(other.value, value);  }  @override  int get hashCode => hashValues(runtimeType, identityHashCode(value));

ObjectKey是继承自LocalKey的,能够将其了解成泛型类型为Object的ValueKey。然而留神两者的==办法是不一样的,ValueKey依据value的值是否相等来判断ValueKey是否相等(相当于java的equals办法),而ObjectKey依据indentical办法(判断两个援用是否指向同一个对象,相当于java的==操作符)来判断两个ObjectKey是否相等的。

UniqueKey

class UniqueKey extends LocalKey {  UniqueKey();  @override  String toString() => '[#${shortHash(this)}]';}

惟一的key,其并未重写==和hashCode办法,所有它只和本人相等。留神看UniqueKey的构造函数,并没有像下面介绍的几个key的构造函数一样应用const润饰,这样做的目标是为了进一步保障UniqueKey的唯一性。这样在调用Element的updateChild办法时,此办法外部调用的Widget.canUpdate办法就会始终返回false,从而每次都会创立新的child element。

所以,如果你想让某一个widget每一次都不复用old element,而是去从新创立新的element,那么就给他增加UniqueKey吧。

const是编译时常量,在编译期,其值就曾经确定。背地利用的相似于常量池的概念,被const润饰的对象会保留在常量池中,前面会对其进行复用。如果UniqueKey构造函数增加了const关键词,那么有如下代码 var k1 = const UniqueKey(); var k2 = const UniqueKey(); 此时k1==k2永远为true,就不能保障其唯一性。

GlobalKey

GlobalKey是全局惟一的,其默认实现是LabeledGlobalKey,所以每次创立的都是新的GlobalKey。所有的GlobalKey都保留在BuildOwner类中的一个map里,此map的key为GlobalKey,此map的value则为GlobalKey关联的element。

对于GlobalKey,须要晓得如下几点:

  • 当领有GlobalKey的widget从tree的一个地位上挪动到另一个地位时,须要reparent它的子树。为了reparent它的子树,必须在一个动画帧里实现从旧地位挪动到新地位的操作。
  • 下面说到的reparent操作是低廉的,因为要调用所有相关联的State和所有子节点的deactive办法,并且所有依赖InheritedWidget的widget去重建。
  • 不要在build办法里创立GlobalKey,性能必定不好,而且也容易呈现意想不到的异样,比方子树里的GestureDetector可能会因为每次build时从新创立GlobalKey而无奈持续追踪手势事件。
  • GlobalKey提供了拜访其关联的Element和State的办法。

上面看下其源码:

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {  ///这里的debugLabel仅仅为了debug时应用  factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);  ///给子类应用的  const GlobalKey.constructor() : super.empty();  Element? get _currentElement => WidgetsBinding.instance!.buildOwner!._globalKeyRegistry[this];  BuildContext? get currentContext => _currentElement;  Widget? get currentWidget => _currentElement?.widget;  T? get currentState {    final Element? element = _currentElement;    if (element is StatefulElement) {      final StatefulElement statefulElement = element;      final State state = statefulElement.state;      if (state is T)        return state;    }    return null; 

其和Key类差不多,也有一个工厂构造函数,默认创立的是LabeledGlobalKey,其构造函数的debugLabel仅仅是为了debug时应用,并不会用来标识element。

如何获取其关联的element?从源码来看,其间接拜访的是BuildOwner里用来保留GlobalKey和Element对应关系的map。获取到了其关联的element,那么就能获取到其对应的widget以及state,具体的能够看下面的源码。

须要留神的是其并没有重写==和hashCode办法,构造函数也没有被const润饰,这也就使LabeledGlobalKey人造就是全局惟一的。

LabeledGlobalKey

这是GlobalKey的默认实现,外部仅有一个debugLabel属性,其余的也没啥。

class LabeledGlobalKey<T extends State<StatefulWidget>> extends GlobalKey<T> {  // ignore: prefer_const_constructors_in_immutables , never use const for this class  LabeledGlobalKey(this._debugLabel) : super.constructor();  final String? _debugLabel;}

GlobalObjectKey

class GlobalObjectKey<T extends State<StatefulWidget>> extends GlobalKey<T> {  const GlobalObjectKey(this.value) : super.constructor();  final Object value;  @override  bool operator ==(Object other) {    if (other.runtimeType != runtimeType)      return false;    return other is GlobalObjectKey<T>        && identical(other.value, value);  }  @override  int get hashCode => identityHashCode(value);

非凡的GlobalKey,重写了==和hashCode办法,外部保护了一个Object对象,通过判断此Object是否指向同一块内存地址来判断两个GlobalObjectKey是否相等。

GlobalKey被要求全局惟一,其默认实现LabeledGloalKey因为其并没有重写==和hashCode办法,也不反对const构造函数,所以人造是全局惟一的。然而GlobalObjectKey不然,如果有两个或者多个中央应用到了领有同一个Object的GlobalObjectKey,那么就不能保障其全局唯一性,造成程序出错。此时,能够继承GlobalObjectKey,实现一个private的外部类,比方:

class _MyGlobalObjectKey extends GlobalObjectKey {  const _MyGlobalObjectKey(Object value) : super(value);}

总结

  • Flutter里的key分为两类,一类是LocalKey,实现类有ValueKey、ObjectKey、UniqueKey;一类是GlobalKey,实现类有LabeledGlobalKey、GlobalObjectKey。
  • Key是所有keys类的基类,其默认实现是String类型的ValueKey。
  • 雷同parent下的key是不能一样的,比方不能再同一个page里应用VlaueKey(1),然而不同parent下是能够存在一样的key的,比方在两个界面里都应用ValueKey(1)。
  • UniqueKey只和本人相等,其并没有重写==和hashCode办法,也没有const润饰的构造函数。当调用Element的updateChild办法时,Widget.canUpdate必定返回false,所以如果你想让widget每次都去创立新的element而不复用old element,那么就给此widget应用UniqueKey。
  • GlobalKey的默认实现是LabeledGlobalKey,其没有实现==和hashCode办法,也没有const润饰的构造函数,所以必定能保障其全局唯一性。
  • 所有的GlobalKey都保留在BuildOwner类中,其外部保护了一个map用来保留GlobalKey与其对应的Element。
  • GlobalObjectKey是非凡的GlobalKey,外部保护了一个Object属性,并实现了==和hashCode办法,通过判断runtimeType以及Object属性是否统一来判断两个GlobalObjectKey是否相等。
  • 应用GlobalObjectKey时,为了保障GlobalObjectKey的全局唯一性,最佳实际是继承自GlobalObjectKey实现一个private的外部类,能够无效防止多人开发时可能造成的GlobalObjectKey抵触的问题。

作者:京东物流 沈亮堂

内容起源:京东云开发者社区