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是用於調試的。

結束

這是第一篇開始深刻框架代碼的文章,代碼閱讀還不是很全面,頗有可能會有錯漏,若有發現,但願可以及時指正。

相關文章
相關標籤/搜索