class ItemCounter extends StatefulWidget { ItemCounter({Key? key, required this.name}) : super(key: key); final String name; @override _ItemCounterState createState() => _ItemCounterState(); } class _ItemCounterState extends State<ItemCounter> { int count = 0; @override Widget build(BuildContext context) { return GestureDetector( child: Text('${widget.name}: $count'), onTap: () { setState(() { count++; }); }, ); } }我们在之前的篇章有讲到实际上渲染控制是通过 Element Tree 完成的,Widget Tree 只是提供配置信息。对于上面的这个例子,实际上 State 对象对外是不可访问的,这个对象实际是被 StatefulElement 持有的。
StatefulElement(StatefulWidget widget) : state = widget.createState(), //...整个组件的各个元素的对应关系如下图所示(以下图片均来自原视频),而 StatefulElement 实际渲染的元素也是一个 StatelessElement。上面的例子一开始其实就是 Text(Tom:0)这么一个渲染的组件(GestureDetector 并不是渲染元素),而此时 State 中的 count 的值是 0。
// 堆代码 duidaima.com @override void didUpdateWidget(covariant ItemCounter oldWidget) { super.didUpdateWidget(oldWidget); }
1.尽可能将组件树的状态维护往下推到叶子节点
这个很好理解,状态维护层级越高,意味着重建的组件树越大。当然是将状态维护放在低层级的叶子节点性能更高。举个例子,假设你页面中有一个每秒定时更新的时钟组件,那么这个时间状态的维护应该单独抽出一个时钟组件,由它自己维护时间状态。
2.最小化 StatefulWidget 的 State 中 build 方法构建的组件的数量
理想情况下,一个 StatefulWidget 应该只有一个子组件,且这个组件对应一个 RenderObject。这个在现实中可能很难满足,但是这是一个指导原则,如果你的 StatefulWidget 的构建了太多组件,那么性能自然而然会下降。这个时候应该要考虑使用状态管理插件进行局部刷新了。
3.如果子组件树在整个生命周期都不改变的话,那么应该考虑将该子组件树缓存起来重复利用
这会比每次重新构建这个子组件树性能好很多。通常的做法是将这个子组件树单独抽离为一个 Widget,然后作为子组件传给 StatefulWidget。尽可能地使用 const 修饰子组件构造方法。这个我们在讲 const 时(解密 Flutter 的 const 关键字)有介绍过。实际上,使用 const 修饰相当于是一种缓存方式。
4.尽可能避免更改子组件树的层级或子组件树中的组件类型
例如在返回子组件时,有可能会根据条件返回子组件或将子组件包裹在 IgnorePointer 中。这种方式其实就是改变了子组件树的层级。应该将子组件统一包裹在 IgnorePointer 中,然后通过IgnorePointer 的ignoring 属性来控制。这是因为,任何更改子组件树深度的操作都会需要重新构建、重新布局、重新绘制整个子组件树。而只更改某个节点的属性的话,将会将改变的范围缩小很多(例如这个例子中,就不需要重新布局和重绘)。
5.假设不得不更改子组件树的层级,那么应该考虑将子组件树中不变的部分使用 GlobalKey 使得这部分在整个 StatefulWidget 的生命周期都保持一致
如果不方便使用 GlobalKey 的话,那么可以考虑使用 KeyedSubtree 组件来应用 GlobalKey。