<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Widget中有個可選屬性key,顧名思義,它是元件的識別符號,當設定了key,元件更新時會根據新老元件的key是否相等來進行更新,可以提高更新效率。但一般我們不會去設定它,除非對某些具備狀態且相同的元件進行新增、移除、或者排序時,就需要使用到key,不然就會出現一些莫名奇妙的問題。
例如下面的demo:
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: 'test', home: Scaffold( appBar: AppBar( title: const Text('key demo'), ), body: const KeyDemo(), ), ); } } class KeyDemo extends StatefulWidget { const KeyDemo({Key? key}) : super(key: key); @override State<StatefulWidget> createState() => _KeyDemo(); } class _KeyDemo extends State<KeyDemo> { final List<ColorBlock> _list = [ const ColorBlock(text: '1'), const ColorBlock(text: '2'), const ColorBlock(text: '3'), const ColorBlock(text: '4'), const ColorBlock(text: '5'), ]; @override Widget build(BuildContext context) { return Column( children: [ ..._list, ElevatedButton( onPressed: () { _list.removeAt(0); setState(() {}); }, child: const Text('刪除'), ) ], ); } } class ColorBlock extends StatefulWidget { final String text; const ColorBlock({Key? key, required this.text}) : super(key: key); @override State<StatefulWidget> createState() => _ColorBlock(); } class _ColorBlock extends State<ColorBlock> { final color = Color.fromRGBO( Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0); @override Widget build(BuildContext context) { return Container( width: double.infinity, height: 50, color: color, child: Text(widget.text), ); } }
點選刪除按鈕,從ColorBlock的列表中刪除第一個元素,可以觀察到顏色發生了錯亂,刪除了1號色塊,它的顏色狀態轉移到了2號身上。這種情況在實際開發中往往會造成不小的麻煩。
這時,就需要為每個ColorBlock設定key值,來避免這個問題。
final List<ColorBlock> _list = [ const ColorBlock(key: ValueKey('1'), text: '1'), const ColorBlock(key: ValueKey('2'), text: '2'), const ColorBlock(key: ValueKey('3'), text: '3'), const ColorBlock(key: ValueKey('4'), text: '4'), const ColorBlock(key: ValueKey('5'), text: '5'), ];
點選刪除按鈕,可以看到顏色錯亂的現象消失了,一切正常。那麼有沒有想過,為什麼ColorBlock有key和沒key會出現這種差異?
我們來簡單分析下key的更新原理。
首先,我們知道Widget是元件設定資訊的描述,而Element才是Widget的真正實現,負責元件的佈局和渲染工作。在建立Widget時會對應的建立Element,Element儲存著Widget的資訊。
當我們更新元件時(通常指呼叫setState方法)會遍歷元件樹,對元件進行新舊設定的對比,如果同個元件資訊不一致,則進行更新操作,反之則不作任何操作。
/// Element Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { if (newWidget == null) { if (child != null) deactivateChild(child); return null; } final Element newChild; /// 更新邏輯走這裡 if (child != null) { bool hasSameSuperclass = true; if (hasSameSuperclass && child.widget == newWidget) { if (child.slot != newSlot) updateSlotForChild(child, newSlot); newChild = child; } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) { /// 判斷新舊元件為同一個元件則進行更新操作 if (child.slot != newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); newChild = child; } else { deactivateChild(child); newChild = inflateWidget(newWidget, newSlot); if (!kReleaseMode && debugProfileBuildsEnabled) Timeline.finishSync(); } } else { /// 建立邏輯走這裡 newChild = inflateWidget(newWidget, newSlot); } return newChild; }
通過Element中的updateChild進行元件的更新操作,其中Widget.canUpdate是判斷元件是否需要更新的核心。
/// Widget static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; }
canUpdate的程式碼很簡單,就是對比新老元件的runtimeType和key是否一致,一致剛表示為同一個元件需要更新。
結合demo,當刪除操作時,列表中第一個的元件oldWidget為ColorBlock(text: '1'),newWidget為ColorBlock(text: '2') ,因為我們將text和color屬性都儲存在State中,所以 oldWidget.runtimeType == newWidget.runtimeType為true,oldWidget.key == newWidget.key 為null,也等於true。
於是呼叫udpate進行更新
/// Element void update(covariant Widget newWidget) { _widget = newWidget; }
可以看出,update也只是簡單的更新Element對Widget的參照。 最終新的widget更新為ColorBlock(text: '2'),State依舊是ColorBlock(text: '1')的State,內部的狀態保持不變。
如果新增了Key,剛oldWidget.key == newWidget.key為false,不會走update流程,也就不存在這個問題。
key有兩個子類GlobalKey和LocalKey。
GlobalKey全域性唯一key,每次build的時候都不會重建,可以長期保持元件的狀態,一般用來進行跨元件存取Widget的狀態。
class GlobalKeyDemo extends StatefulWidget { const GlobalKeyDemo({Key? key}) : super(key: key); @override State<StatefulWidget> createState() => _GlobalKeyDemo(); } class _GlobalKeyDemo extends State<GlobalKeyDemo> { GlobalKey _globalKey = GlobalKey(); @override Widget build(BuildContext context) { return Column( children: [ ColorBlock( key: _globalKey, ), ElevatedButton( onPressed: () { /// 通過GlobalKey可以存取元件ColorBlock的內部 (_globalKey.currentState as _ColorBlock).setColor(); setState(() {}); }, child: const Text('更新為紅色'), ) ], ); } } class ColorBlock extends StatefulWidget { const ColorBlock({Key? key}) : super(key: key); @override State<StatefulWidget> createState() => _ColorBlock(); } class _ColorBlock extends State<ColorBlock> { Color color = Colors.blue; setColor() { color = Colors.red; } @override Widget build(BuildContext context) { return Container( width: double.infinity, height: 50, color: color, ); } }
將元件的key設定為GlobalKey,可以通過範例存取元件的內部屬性和方法。達到跨元件操作的目的。
LocalKey區域性key,可以保持當前元件內的子元件狀態,用法跟GlobalKey類似,可以存取元件內部的資料。
LocalKey有3個子類ValueKey、ObjectKey、UniqueKey。
可以使用任何值做為key,比較的是兩個值之間是否相等於。
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; } /// ... }
可以使用Object物件作為Key,比較的是兩個物件記憶體地址是否相同,也就是說兩個物件是否來自同一個類的參照。
class ObjectKey extends LocalKey { const ObjectKey(this.value); final Object? value; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; /// identical函數: 檢查兩個參照是否指向同一物件 return other is ObjectKey && identical(other.value, value); } /// ... }
獨一無二的key,Key的唯一性,一旦使用UniqueKey,那麼將不存在element複用
class UniqueKey extends LocalKey { UniqueKey(); @override String toString() => '[#${shortHash(this)}]'; }
1、key是Widget中的唯一標識,如果列表中包含有狀態元件,對其進行新增、移除、或者排序操作,必須增加key。以避免出現亂序現象。
2、出現亂序現象的根本原因是:新舊元件通過runtimeType和key進行對比,key為空的情況下,有狀態元件runtimeType對比為true,造成元件更新後依然保持State內部的屬性狀態。
3、key分為GlobalKey和LocalKey,GlobalKey可以進行跨元件存取Widget,LocalKey只能在同級之下進行。
以上就是詳解Flutter中key的正確使用方式的詳細內容,更多關於Flutter key使用方式的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45