網(wǎng)站首頁 編程語言 正文
1、什么是key
Widget中有個可選屬性key,顧名思義,它是組件的標識符,當設置了key,組件更新時會根據(jù)新老組件的key是否相等來進行更新,可以提高更新效率。但一般我們不會去設置它,除非對某些具備狀態(tài)且相同的組件進行添加、移除、或者排序時,就需要使用到key,不然就會出現(xiàn)一些莫名奇妙的問題。
例如下面的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的列表中刪除第一個元素,可以觀察到顏色發(fā)生了錯亂,刪除了1號色塊,它的顏色狀態(tài)轉(zhuǎn)移到了2號身上。這種情況在實際開發(fā)中往往會造成不小的麻煩。
這時,就需要為每個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'),
];
點擊刪除按鈕,可以看到顏色錯亂的現(xiàn)象消失了,一切正常。那么有沒有想過,為什么ColorBlock有key和沒key會出現(xiàn)這種差異?
2、key的更新原理
我們來簡單分析下key的更新原理。
首先,我們知道Widget是組件配置信息的描述,而Element才是Widget的真正實現(xiàn),負責組件的布局和渲染工作。在創(chuàng)建Widget時會對應的創(chuàng)建Element,Element保存著Widget的信息。
當我們更新組件時(通常指調(diào)用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 {
/// 創(chuàng)建邏輯走這里
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是否一致,一致剛表示為同一個組件需要更新。
結(jié)合demo,當刪除操作時,列表中第一個的組件oldWidget為ColorBlock(text: '1'),newWidget為ColorBlock(text: '2') ,因為我們將text和color屬性都存儲在State中,所以 oldWidget.runtimeType == newWidget.runtimeType為true,oldWidget.key == newWidget.key 為null,也等于true。
于是調(diào)用udpate進行更新
/// Element
void update(covariant Widget newWidget) {
_widget = newWidget;
}
可以看出,update也只是簡單的更新Element對Widget的引用。 最終新的widget更新為ColorBlock(text: '2'),State依舊是ColorBlock(text: '1')的State,內(nèi)部的狀態(tài)保持不變。
如果添加了Key,剛oldWidget.key == newWidget.key為false,不會走update流程,也就不存在這個問題。
3、key的分類
key有兩個子類GlobalKey和LocalKey。
GlobalKey
GlobalKey全局唯一key,每次build的時候都不會重建,可以長期保持組件的狀態(tài),一般用來進行跨組件訪問Widget的狀態(tài)。
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的內(nèi)部
(_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,可以通過實例訪問組件的內(nèi)部屬性和方法。達到跨組件操作的目的。
LocalKey
LocalKey局部key,可以保持當前組件內(nèi)的子組件狀態(tài),用法跟GlobalKey類似,可以訪問組件內(nèi)部的數(shù)據(jù)。
LocalKey有3個子類ValueKey、ObjectKey、UniqueKey。
- ValueKey
可以使用任何值做為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;
}
/// ...
}
- ObjectKey:
可以使用Object對象作為Key,比較的是兩個對象內(nèi)存地址是否相同,也就是說兩個對象是否來自同一個類的引用。
class ObjectKey extends LocalKey {
const ObjectKey(this.value);
final Object? value;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
/// identical函數(shù): 檢查兩個引用是否指向同一對象
return other is ObjectKey
&& identical(other.value, value);
}
/// ...
}
- UniqueKey
獨一無二的key,Key的唯一性,一旦使用UniqueKey,那么將不存在element復用
class UniqueKey extends LocalKey {
UniqueKey();
@override
String toString() => '[#${shortHash(this)}]';
}
總結(jié)
1、key是Widget中的唯一標識,如果列表中包含有狀態(tài)組件,對其進行添加、移除、或者排序操作,必須增加key。以避免出現(xiàn)亂序現(xiàn)象。
2、出現(xiàn)亂序現(xiàn)象的根本原因是:新舊組件通過runtimeType和key進行對比,key為空的情況下,有狀態(tài)組件runtimeType對比為true,造成組件更新后依然保持State內(nèi)部的屬性狀態(tài)。
3、key分為GlobalKey和LocalKey,GlobalKey可以進行跨組件訪問Widget,LocalKey只能在同級之下進行。
原文鏈接:https://juejin.cn/post/7189816647891288123
相關推薦
- 2022-08-01 Python3?中return和yield的區(qū)別_python
- 2022-05-02 Entity?Framework中執(zhí)行sql語句_實用技巧
- 2022-07-14 React?Native?Popup實現(xiàn)示例_React
- 2022-04-11 jackson中對null的處理
- 2022-12-04 Python學習之字典和集合的使用詳解_python
- 2022-02-21 小程序頁面跳轉(zhuǎn)如何同時傳多個參數(shù)?
- 2023-01-09 GO中優(yōu)雅編碼與降低圈復雜度詳析_Golang
- 2022-08-06 C語言詳解關鍵字sizeof與unsigned及signed的用法_C 語言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支