美文网首页flutter牛逼
Flutter中key的作用

Flutter中key的作用

作者: iOSer_jia | 来源:发表于2020-06-18 15:38 被阅读0次

    概述

    在Widget的构造方法中,有Key这么一个可选参数,Key是一个抽象类,有LocalKey和GlobalKey两种,本文将对这两种key的作用进行探究。

    LocalKey

    在探究LocalKey的作用之前,先通过一段代码来看看一个场景

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

    这段代码中创建了3个颜色不同,文本不同的子widget,按照从左往右排列,要注意的是,颜色对象保存在Sate中,而文本对象保存在Widget中,然后点击按钮,每次删除最左边的widget。

    执行结果出乎意料:


    widget_initial.png widget_first.png widget_second.png widget_last.png

    虽然每次都能删除文本正确的widget,但是颜色值确实下一个widget的颜色,这种奇怪现象的原因其实和Flutter的增量渲染机制有关,我们知道widget树是对应着element树的,而widget树是不稳定的,widget在每次的刷新中都有可能在创建或者销毁,而element不会,他会去对比树中新的widget和旧widget是否一致,是否可以更新widget,如果可以更新,那么element将会指向新的widget。如下图:

    widget_tree_1.png element_tree_1.png-w400

    一开始,widget树的节点与element树的节点是一一对应的,element的_widget属性指向了对应的widget,当widget树发生改变

    widget_tree_2.png

    element树并不会全部重新创建,而是与从左到右比较,看是否新的节点的widget与原先的节点的widget是否一致,而判断的依据我们可以在源码中看到

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

    他比较的新旧widget的runtimeType和key值,明显widgetA和widgetB的runtimeType和key均一致,所以element会变成这样


    element_tree_2.png

    element会复用,而widget则换成新的widget,当state并没有改变,而在案例中颜色是存放在sate中的,所以,首个element的文本变成了widgetB的文本,但是颜色还是sateA的的颜色,而widgetC的颜色变成了stateB的颜色,而由于原先3个节点变成两个,所以最后一个element被移除,最终变成我们看到的结果。

    要解决这个问题,我们只需要在Flutter判断是否更新widget的时候给一个false的结果,就能解决这个问题,两个对比条件中,runtimeType肯定是一致的,所以,可以给widget添加一个唯一标示key

    我们只需要对代码做下面的修改

    ...
    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();
    }
    ...
    

    这样,结果就是正常的了。

    从上面的案例可以看出,LocalKey可以给Widget作为唯一表示,在element树更新能准确的更新对应正确的widget。

    LocalKey除了ValueKey,还有ObjectKey和UniqueKey两种类型,其实效果都一样,只是有一些差别

    • ValueKey: 以一个数据作为Key,如:数字、字符
    • ObjectKey: 以Object对象作为Key
    • UniqueKey: 可以保证Key的唯一性,一旦使用UniqueKey那么就不存在Element复用了

    GlobalKey

    GlobalKey可以获取到对应的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;
      }
    }
    

    而利用这个特性,我们可以实现局部刷新从而进行优化,比如如果只是根widget的按钮被点击,而需要改变的仅仅是子widget,我们并不需要刷新整个widget树,可以通过GlobalKey拿到对应的sate,仅仅刷新子widget的状态,从而优化性能。

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

    总结

    以上,便是我对key的探究,Key是一个抽象类,有LocalKey和GlobalKey两个子类,LocalKey可以作为Widget的唯一标示,避免Element的重用,而GlobalKey可以拿到指定的Widget、Element、State。

    相关文章

      网友评论

        本文标题:Flutter中key的作用

        本文链接:https://www.haomeiwen.com/subject/cwyuxktx.html