Flutter 中 key 的原理及作用

866 阅读2分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

Key 的原理

image.png

image.png

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

如图 1 所示,当我们生成一个 Widget 树的时候也会对应生成 Element 树,WidgetElement 一一对应。但是当我们移除 Widget1 的时候会调用 ElementcanUpdate 方法,canUpdate 方法中会判断旧的 Widget 与新的 WidgetruntimeType 是否相等,且 key 是否相等。当我们不使用 key 时候,就会默认它们相等,就会如图 2 所示,Element1 就会比较 oldWidget.runtimeType == newWidget.runtimeType,因为 Element1 曾经指向的类型与 Widget2 类型相同所以 Widget2 就会复用 Element1,同理 Widget3 就会复用 Element2,依次比较,当 Element3 没有指向的时候就会被移除。所以 Key 的作用可以用来跟 Widget 做绑定,判断 canUpdate 是否执行。

  • Key 本身是一个抽象类,有一个工厂程构造方法 ValueKey()
  • 直接子类主要有:LocalKeyGlobalKey
  • LocalKey 是增量算法的核心,决定哪个 Element 要保留,哪个 Element 要删除。以下是 LocalKey 的三个子类。
    • ValueKey:以值作为参数(数字、字符串)
    • ObjectKey:以对象作为参数
    • UniqueKey:创建唯一标识
  • GlobalKey 对应某一个 Widget 或者 State 或者 Element

GlobalKey 的使用

class GlobalKeyDemo extends StatelessWidget {
  final GlobalKey<_ChildPageState> _globalKey = GlobalKey();
  
  GlobalKeyDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GlobalKeyDemo'),
      ),
      body: ChildPage(key: _globalKey,),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // _globalKey.currentContext
          // _globalKey.currentWidget
          _globalKey.currentState?.setState(() {
            _globalKey.currentState?.data = '666';
            _globalKey.currentState?.count++;
          });
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

class ChildPage extends StatefulWidget {
  const ChildPage({Key? key}) : super(key: key);

  @override
  _ChildPageState createState() => _ChildPageState();
}

class _ChildPageState extends State<ChildPage> {
  int count = 0;
  String data = 'hello';
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          Text(count.toString()),
          Text(data)
        ],
      ),
    );
  }
}

如案例所示,当 FloatingActionButtononPressed 闭包函数执行的时候我们想修改 _ChildPageStatecountdata 的值,我们可以在 GlobalKeyDemo 中定义 _globalKey = GlobalKey(),在初始化 ChildPage 的时候把 _globalKey 作为参数传递。这时候我们可以通过 _globalKey.currentContext_globalKey.currentWidget_globalKey.currentState 获取我们想拿到的部件,这里我们通过 _globalKey.currentState就能获取到 _ChildPageState 修改 datacount 属性,并调用 setState 方法。个人感受 Flutter 中的 GlobalKey 有点类似 iOSUIViewtag 属性,可以通过 tag 来获取到对应的 UIView 控件。