笔者之前是java后端开发,转flutter之后又被公司裁员,当时对flutter知之甚少,去面试没看面经遂被第一题考倒:flutter中widget的key有什么作用?
查阅了网上很多描述都不太理解,最近在某视频学习网站看了一个讲解视频才略有了解。
没有key的情况下
先上代码,以下代码自定义了组件Box,Box中用GestureDetector包裹了一个有定义颜色的Container,每点击Box一次就会使的里面的数字+1.
js
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: MyHome(),
),
);
}
class MyHome extends StatefulWidget {
const MyHome({super.key});
@override
State<MyHome> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Box(Colors.red),
Box(Colors.yellow),
Box(Colors.blue),
],
),
),
);
}
}
class Box extends StatefulWidget {
const Box(this.color); // 接收并传递 key
final Color color;
@override
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
_counter++;
setState(() {});
},
child: GestureDetector(
onTap: () {
_counter++;
setState(() {});
},
child: Container(
width: 80,
height: 80,
color: widget.color,
child: Text("$_counter"),
),
),
);
}
}
把以上代码运行起来,再从上往下依次点击1、2、3次,我们会得到这么一个页面
此时我们再把_MyHomeState中的builder方法里的Box(Colors.yellow)注释掉,再hot reload一下,各位读者可以猜下页面会变成什么样
js
class _MyHomeState extends State<MyHome> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Box(Colors.red),
// Box(Colors.yellow),
Box(Colors.blue),
],
),
),
);
}
}
如果各位读者像我一样对flutter的key不甚了解,可能会认为,页面上从上往下依次是:红1、蓝3,然而页面变成了这样
为什么会变成这样呢,这正是因为没有指定key导致的。如下图所示,我们在创建widget树的时候也会创建对应的element树,而状态是保存在elemnet树中的
当我们删除了黄色的box之后,蓝色的box就顺理成章上移动一格,匹配到右边的的state = 2的box element。而state = 3的boxelement因为找不到对应的widget也被释放掉了。
正是因为上图所说没匹配到的elemnet被释放了,我们如果把之前注释的代码加回来,再hot reload一下,得到的不是红1、黄2、蓝3,而是红1、黄2、蓝0(初始值为0)
添加了key的情况下
简单修改代码(26、27、28、37行),给Box传递参数key
js
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: MyHome(),
),
);
}
class MyHome extends StatefulWidget {
const MyHome({super.key});
@override
State<MyHome> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Box(Colors.red, key: ValueKey(1),),
Box(Colors.yellow, key: ValueKey(2),),
Box(Colors.blue, key: ValueKey(3),),
],
),
),
);
}
}
class Box extends StatefulWidget {
const Box(this.color, {super.key});
final Color color;
@override
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
_counter++;
setState(() {});
},
child: GestureDetector(
onTap: () {
_counter++;
setState(() {});
},
child: Container(
width: 80,
height: 80,
color: widget.color,
child: Text("$_counter"),
),
),
);
}
}
运行程序,从上往下分别点击1、2、3次,得到红1黄2蓝3
此时再把黄色的box注释掉,hot reload一下,就会得到红1蓝3,符合我们的预期
这是因为加了key之后,widget和element不再通过顺序匹配了,而是通过key来匹配,所以这次是key=2的element被释放了