Flutter中的Key,LocalKey,GlobalKey... And More

开始

从这一篇文章开始,花时间慢慢阅读源码,从web前端角度看Flutter,而后也把一些收获也分享给你们。
React和React Native受到Facebook条款限制,大公司们(主要BAT)都开始如有所思,RN也彷佛一下掉下了神坛,同志们,此时此刻正是Flutter当立的时候,你们一块儿跨进新的时代!前端

各类各样的Key

立马跳到framework.dart文件(这个文件一看名字就很重要啦),Flutter代码的组织并不像Java反而倾向于JS这种组织方式,一个文件里面塞着不一样的Class(固然他们之间确定有联系的),其实我的更倾向于Java那种一个类一个文件,阅读和分析感受都比较方便舒服。
framework.dart一开始你就会遇到几个名词:Key,LocalKey,UniqueKey等等。
clipboard.pnggit

一会儿冒出几个兄弟来,得一个一个分清楚他们的各有什么能力。github

key的做用

首先key有何用尼?
Flutter是受React启发的,因此Virtual Dom的diff算法也参考过来了(应该是略有修改),在diff的过程当中若是节点有Key来比较的话,可以最大程度重用已有的节点(特别在列表的场景),除了这一点这个Key也用在不少其余的地方这个之后会总结一下。总之,这里咱们能够知道key可以提升性能,因此每一个Widget都会构建方法都会有一个key的参数可选,贯穿着整个框架。web

key之间的关系

先上图:算法

clipboard.png

这里就是Key的类型层级关系,能够看到:
Key有两个重要的子类:LocalKey 和 GlobalKey,而他们各自也有不一样的子类实现,接下来会继续深刻分析。缓存

普通的Key

他们的老大哥Key,这个Key的实现有点特别。框架

@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 Key() factory
  /// constructor shadows the implicit constructor.
  const Key._();
}

看上去很简单,没有什么特别方法的实现,那为啥特别尼,我我的认为这种特别来自Dart语言的一些特性(实际上是Dart语言基础太浅,大神用的太溜)。ide

const Key._();

首先这里用到命名构造函数(named constructors),大体做用就是给构造函数加多一个有意义的名称,可以让使用者更容易明白各个构造函数的区别(由于相似Java这样,只能靠参数列表来区分确实容易形成混乱)。这里是一个空的实现(并非Java那一种抽象方法)这里来源一个建议 传送门;函数

const factory Key(String value) = ValueKey<String>;

这里就是Key默认构造函数(只能有一个默认构造函数,哪怕修改参数列表也不行,以后你只能定义命名构造函数了),可是跟Java又有点不同啊,首先是factory这个关键字,这是Dart语言内置了对工厂模式的支持(其实大部分语言均可以支持这种模式,这里语言层面上再强化了),而加了这个关键字会怎样?咱们知道构建方法返回的通常都是当前类所刚构建的对象,可是加上factory关键字以后你能够修改返回的值,可让返回的对象是以前已经建立好的,也能够返回这个类的子类对象。
这里还有涉及到一个factory redirect(官网貌似没有介绍,估计新加的语法)传送门
等效于性能

const factory Key(String value) => new ValueKey<String>(value);

因此这里其实返回了一个ValueKey。

ValueKey

顺藤摸瓜来到ValueKey,而ValueKey其实就是LocalKey的一个子类,可是LocalKey并无特别的实现只是简单调用了Key._()构造函数。而ValueKey则是:

class ValueKey<T> extends LocalKey

构造函数须要传入一个value的参数:

const ValueKey(this.value);

ValueKey是一个泛型类,而且覆盖了自身的operator==方法(跟 C++覆盖操做符同样),调用了目标类型T的运算符==来比较,固然覆盖了operator==方法也须要覆盖获取hashCode的方法(道理跟Java同样)。因此ValueKey的比较取决于value的operator==的实现,例如Value是字符串类型那就是内容的比较。

ObjectKey

构造函数跟ValueKey差很少:

const ObjectKey(this.value);

虽然一样覆盖了自身的operator==方法,可是它调用的是identical()方法,因此比较的是value的引用。

UniqueKey

也没有特别的实现,没有覆盖operator==方法,因此UniqueKey比较的时候,也就比较引用了(Object默认的operator==调用的就是identical()方法)。

GlobalKey

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key

也是一个泛型类型,可是T必需要继承自State<StatefulWidget>,能够说这个GlobalKey专门用于组件了。
再看:

static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};

GlobalKey里含有一个Map,key和value分别为自身和Element。
那何时会用到这个Map尼?
跟踪代码很快就找到Element类的mount方法:

void mount(Element parent, dynamic newSlot) {
    ...
    if (widget.key is GlobalKey) {
      final GlobalKey key = widget.key;
      key._register(this);
    }
   ...
  }

可见GlobalKey会在组件Mount阶段把自身放到一个Map里面缓存起来。
缓存又有何做用尼?
答案依然是为了性能。
思考一个场景,A页面是一个商品列表有许多商品图片(大概就单列这样),B页面是一个商品详情页(有商品大图),当用户在A页面点击一个其中详情,可能会出现一个过渡动画,A页面的商品图片慢慢放大而后下面的介绍文字也会跟着出现,而后就这样平滑的过渡到B页面。
此时A页面和B页面都其实共用了一个商品图片的组件,B页面不必重复建立这个组件能够直接把A页面的组件“借”过来。
使用GlobalKey的组件生命周期是如何的尼,这里暂时挖一个坑先,哈哈。
总之框架要求同一个父节点下子节点的Key都是惟一的就能够了,GlobalKey能够保证全局是惟一的,因此GlobalKey的组件可以依附在不一样的节点上。
而从GlobalKey对象上,你能够获得几个有用的属性currentElement,currentWidget,currentState。

GlobalObjectKey

GlobalObjectKey跟LocalObjectKey也差很少,不一样点就在与它是Global的。

LabeledGlobalKey

LabeledGlobalKey是用于调试的。

结束

这是第一篇开始深刻框架代码的文章,代码阅读还不是很全面,颇有可能会有错漏,若有发现,但愿可以及时指正。

相关文章
相关标签/搜索