Flutter中key的作用

概述

在Widget的構(gòu)造方法中隅忿,有Key這么一個(gè)可選參數(shù)迫靖,Key是一個(gè)抽象類懊蒸,有LocalKey和GlobalKey兩種拇泛,本文將對(duì)這兩種key的作用進(jìn)行探究滨巴。

LocalKey

在探究LocalKey的作用之前,先通過一段代碼來看看一個(gè)場(chǎng)景

class LocalKeyDemo extends StatefulWidget {
  @override
  _LocalKeyDemoState createState() => _LocalKeyDemoState();
}

class _LocalKeyDemoState extends State<LocalKeyDemo> {
  List<TitleItem> _items = [
    TitleItem(title: "aaaaa"),
    TitleItem(title: "bbbbb"),
    TitleItem(title: "ccccc"),
  ];

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Scaffold(
        appBar: AppBar(
          title: Text("Key"),
        ),
        body: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: _items,
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: (){
            setState(() {
              _items.removeAt(0);
            });
          },
          child: Icon(Icons.delete),
        ),
      ),
    );
  }
}

class TitleItem extends StatefulWidget {
  final String title;
  TitleItem({this.title});

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

class _TitleItemState extends State<TitleItem> {
  final Color _randomColor = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: _randomColor,
      width: 100,
      height: 100,
      child: Center(
        child: Text(widget.title),
      ),
    );
  }
}

這段代碼中創(chuàng)建了3個(gè)顏色不同俺叭,文本不同的子widget恭取,按照從左往右排列,要注意的是熄守,顏色對(duì)象保存在Sate中蜈垮,而文本對(duì)象保存在Widget中耗跛,然后點(diǎn)擊按鈕,每次刪除最左邊的widget攒发。

執(zhí)行結(jié)果出乎意料:


widget_initial.png

widget_first.png
widget_second.png
widget_last.png

雖然每次都能刪除文本正確的widget调塌,但是顏色值確實(shí)下一個(gè)widget的顏色,這種奇怪現(xiàn)象的原因其實(shí)和Flutter的增量渲染機(jī)制有關(guān)惠猿,我們知道widget樹是對(duì)應(yīng)著element樹的羔砾,而widget樹是不穩(wěn)定的,widget在每次的刷新中都有可能在創(chuàng)建或者銷毀偶妖,而element不會(huì)姜凄,他會(huì)去對(duì)比樹中新的widget和舊widget是否一致,是否可以更新widget趾访,如果可以更新檀葛,那么element將會(huì)指向新的widget。如下圖:

widget_tree_1.png

element_tree_1.png-w400

一開始腹缩,widget樹的節(jié)點(diǎn)與element樹的節(jié)點(diǎn)是一一對(duì)應(yīng)的屿聋,element的_widget屬性指向了對(duì)應(yīng)的widget,當(dāng)widget樹發(fā)生改變

widget_tree_2.png

element樹并不會(huì)全部重新創(chuàng)建藏鹊,而是與從左到右比較润讥,看是否新的節(jié)點(diǎn)的widget與原先的節(jié)點(diǎn)的widget是否一致,而判斷的依據(jù)我們可以在源碼中看到

static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

他比較的新舊widget的runtimeType和key值盘寡,明顯widgetA和widgetB的runtimeType和key均一致楚殿,所以element會(huì)變成這樣


element_tree_2.png

element會(huì)復(fù)用,而widget則換成新的widget竿痰,當(dāng)state并沒有改變脆粥,而在案例中顏色是存放在sate中的,所以影涉,首個(gè)element的文本變成了widgetB的文本变隔,但是顏色還是sateA的的顏色,而widgetC的顏色變成了stateB的顏色蟹倾,而由于原先3個(gè)節(jié)點(diǎn)變成兩個(gè)匣缘,所以最后一個(gè)element被移除,最終變成我們看到的結(jié)果鲜棠。

要解決這個(gè)問題肌厨,我們只需要在Flutter判斷是否更新widget的時(shí)候給一個(gè)false的結(jié)果,就能解決這個(gè)問題豁陆,兩個(gè)對(duì)比條件中柑爸,runtimeType肯定是一致的,所以盒音,可以給widget添加一個(gè)唯一標(biāo)示key

我們只需要對(duì)代碼做下面的修改

...
class _LocalKeyDemoState extends State<LocalKeyDemo> {
  List<TitleItem> _items = [
    TitleItem(title: "aaaaa", key: ValueKey(1),),
    TitleItem(title: "bbbbb", key: ValueKey(2),),
    TitleItem(title: "ccccc", key: ValueKey(3),),
  ];
...
}
...
class TitleItem extends StatefulWidget {
  final String title;
  TitleItem({this.title, Key key}) : super(key: key);

  @override
  _TitleItemState createState() => _TitleItemState();
}
...

這樣表鳍,結(jié)果就是正常的了何址。

從上面的案例可以看出,LocalKey可以給Widget作為唯一表示进胯,在element樹更新能準(zhǔn)確的更新對(duì)應(yīng)正確的widget。

LocalKey除了ValueKey原押,還有ObjectKey和UniqueKey兩種類型胁镐,其實(shí)效果都一樣,只是有一些差別

  • ValueKey: 以一個(gè)數(shù)據(jù)作為Key诸衔,如:數(shù)字盯漂、字符
  • ObjectKey: 以O(shè)bject對(duì)象作為Key
  • UniqueKey: 可以保證Key的唯一性,一旦使用UniqueKey那么就不存在Element復(fù)用了

GlobalKey

GlobalKey可以獲取到對(duì)應(yīng)的Sate,Element以及Widget笨农,

  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;
  }
}

而利用這個(gè)特性就缆,我們可以實(shí)現(xiàn)局部刷新從而進(jìn)行優(yōu)化,比如如果只是根widget的按鈕被點(diǎn)擊谒亦,而需要改變的僅僅是子widget竭宰,我們并不需要刷新整個(gè)widget樹,可以通過GlobalKey拿到對(duì)應(yīng)的sate份招,僅僅刷新子widget的狀態(tài)切揭,從而優(yōu)化性能。

class GlobalKeyDemo extends StatelessWidget {
  final GlobalKey<_ChildPageState> _globalKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Scaffold(
        appBar: AppBar(
          title: Text("GlobalKey"),
        ),
        body: ChildPage(key: _globalKey),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            _globalKey.currentState.setState(() {
              _globalKey.currentState.count ++;
            });
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

class ChildPage extends StatefulWidget {
  ChildPage({Key key}):super(key: key);

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

class _ChildPageState extends State<ChildPage> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text(count.toString(), style: TextStyle(fontSize: 30, fontWeight: FontWeight.w800, color: Colors.blue),),
      ),
    );
  }
}

總結(jié)

以上锁摔,便是我對(duì)key的探究廓旬,Key是一個(gè)抽象類,有LocalKey和GlobalKey兩個(gè)子類谐腰,LocalKey可以作為Widget的唯一標(biāo)示孕豹,避免Element的重用,而GlobalKey可以拿到指定的Widget十气、Element励背、State。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末砸西,一起剝皮案震驚了整個(gè)濱河市椅野,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌籍胯,老刑警劉巖竟闪,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異杖狼,居然都是意外死亡炼蛤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蝶涩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來理朋,“玉大人絮识,你說我怎么就攤上這事∷陨希” “怎么了次舌?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)兽愤。 經(jīng)常有香客問我彼念,道長(zhǎng),這世上最難降的妖魔是什么浅萧? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任逐沙,我火速辦了婚禮,結(jié)果婚禮上洼畅,老公的妹妹穿的比我還像新娘吩案。我一直安慰自己,他們只是感情好帝簇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布徘郭。 她就那樣靜靜地躺著,像睡著了一般丧肴。 火紅的嫁衣襯著肌膚如雪崎岂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天闪湾,我揣著相機(jī)與錄音冲甘,去河邊找鬼。 笑死途样,一個(gè)胖子當(dāng)著我的面吹牛江醇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播何暇,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼陶夜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了裆站?” 一聲冷哼從身側(cè)響起条辟,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宏胯,沒想到半個(gè)月后羽嫡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肩袍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年杭棵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氛赐。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡魂爪,死狀恐怖先舷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滓侍,我是刑警寧澤蒋川,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站撩笆,受9級(jí)特大地震影響捺球,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浇衬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望餐济。 院中可真熱鬧耘擂,春花似錦、人聲如沸絮姆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)篙悯。三九已至蚁阳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸽照,已是汗流浹背螺捐。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矮燎,地道東北人定血。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像诞外,于是被迫代替她去往敵國(guó)和親澜沟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355