理解Flutter的key

Flutter的所有Widget的构造函数都有一个optional的Key参数,你可以指定,也可以不指定。

在用Flutter进行开发时,大多数情况下,我们并不需要为widget指定key。但今天我却碰到了一个由key引发的问题。我有一个ListView,我要让用户可以删掉其中的任何item。当我将某个数据从数组中删掉,然后setState时,发现列表没有变化。因为之前有写过一点React,知道可能是Widget的key导致的,于是给列表里的每个item加了key,问题立马就解决了。问题解决并不是终点,我们需要搞清楚Flutter的key到底是个什么东西。

Flutter的UI有2棵树,一棵Widget树,一颗Element树。Flutter实际上是用Element树以及它对应的State来渲染UI的。

Element类有对应Widget的引用:

Element类也有对应Widget State的引用:

让我们用一个简单的demo来演示没有key导致的问题。我有2个色块,我点击一个按钮去交换这两个按钮的位置。

void main() => runApp(new MaterialApp(home: PositionedTiles()));

class PositionedTiles extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => PositionedTilesState();
}

class PositionedTilesState extends State<PositionedTiles> {
  List<Widget> tiles = [
    StatefulColorfulTile(),
    StatefulColorfulTile(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(children: tiles),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles),
    );
  }

  swapTiles() {
    setState(() {
      tiles.insert(1, tiles.removeAt(0));
    });
  }
}

class StatefulColorfulTile extends StatefulWidget {
  @override
  ColorfulTileState createState() => ColorfulTileState();
}

class ColorfulTileState extends State<ColorfulTile> {
  Color myColor;

  @override
  void initState() {
    super.initState();
    myColor = UniqueColorGenerator.getColor();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        color: myColor,
        child: Padding(
          padding: EdgeInsets.all(70.0),
        ));
  }
}

实际结果是,点击后,这2个色块并不会交换位置。

 

当色块用StatelessWidget时,是没有问题的,色块可以正常交换位置。原因是,当Widget tree变化之后,Flutter框架开始遍历Element tree,对比它跟Widget tree的差异,并做更新。对比差异时,框架会对比Widget的类型和key,但因为没有指定key,所以只对比类型。框架发现Element tree里第一个Tile的类型跟Widget tree里的一样,于是就更新它的Widget引用,这时就引用到了交换后的色块了。第二个Element元素也是一样的过程,会更新引用,引用交换后的色块。这就是为什么用StatelessWidget没有问题的原因。

 

但StatefulWidget的情况不一样。StatefulWidget的State并不是由Widget引用的,而是由Element引用的,如下图所示:

 

这时虽然Element指向了交换后Widget,但引用的State类并没有变,所以框架在渲染时并没有变化。

 

当我们给每个Widget加了key之后,情况就不一样了。框架在对比Element和Widget时,发现key不一样,于是将这个Element从Element tree里移除。这样的话,2个色块的Element都会被移除。之后框架得重建这些Element。为了提高框架的效率,必须有Element的复用机制,所以框架会在同一个父Element之下被移除的子元素中找key一样的,找到后Element tree再引用它们。这样的话Element tree里的2个Element就交换了位置,渲染到屏幕上时也就对了。

 

所以请记住:

当你需要插入、删除、重排序一个类型相同,且是Stateful的widget列表时,你需要给每个widget指定key。

 

References:

https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值