1、前言
本文在阅读完多篇性能优化文章后结合自身项目做了一些总结归纳。说白了就是局部更新、区域自治和前端的组件化、模块化。具体可以参考官方文档或是观看这个视频:Flutter的性能测试和理论。
2、重建最小化(控制刷新范围)
我们使用setState方法就可以轻松刷新页面,但是要尽力控制刷新范围。我举一个例子:
在订单页面会有一个支付倒计时,需要每隔一秒刷新一下倒计时显示的数字。
如果倒计时逻辑处理放在页面层级,那么每当setState时都是整个页面的刷新。而这整页刷新显然是不必要的。而它并不会让你感知到卡顿,所以也不易发现。
解决方法就是将这个倒计时的按钮单独封装到一个StatefulWidget,在这个StatefulWidget中使用setState刷新,控制刷新范围。
同样的,你也可以使用provider等状态管理框架来实现局部刷新。精准控制你的刷新范围,千万不要任何啥时候setState刷新整个页面层级。
3、控制刷新次数
比起控制刷新范围,控制刷新次数(避免无效刷新)甚至更加重要。
那么我们的做法就是监听TextField的文字输入,每次输入时判断是否满足条件,更新按钮是否可点击的状态。
4、避免更改组件树的结构和组件的类型
bool _visible = true;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
if(_visible)
Text('可见'),
Container(),
],
),
);
}
组件树三级节点显示逻辑:
两种状态组件树结构发生变化,应该避免发生此种情况,优化如下:
Center(
child: Column(
children: [
Visibility(
visible: _visible,
child: Text('可见'),
),
Container(),
],
),
)
此时不管是可见还是不可见状态,组件树都不会发生变化,如下:
还有一种情况是根据不同的条件构建不同的组件,如下:
bool _showButton = true;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
_showButton ? RaisedButton(onPressed: null) : Text('不显示'),
Container(),
],
),
);
}
设置为 true 时的组件树结构:
设置为 false 时的组件树结构:
上面的情况组件树发生了更改,不管是类型发生更改,还是深度发生更改,如果无法避免,那么就将变化的组件树封装为一个 StatefulWidget 组件,且设置 GlobalKey,如下:
封装变化的部分:
class ChildWidget extends StatefulWidget {
const ChildWidget({Key key}) : super(key: key);
@override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
bool _showButton = true;
@override
Widget build(BuildContext context) {
return _showButton ? RaisedButton(onPressed: null) : Text('不显示');
}
}
调用:
class ConstDemo extends StatefulWidget {
@override
_ConstDemoState createState() => _ConstDemoState();
}
class _ConstDemoState extends State<ConstDemo> {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
ChildWidget(key: ValueKey(),),
Container(),
],
),
);
}
}
虽然通过 GlobalKey 提高了上面案例的性能,但我们千万不要乱用 GlobalKey,因为管理 GlobalKey 的成本很高,所以其他需要使用 Key 的地方建议考虑使用 Key, ValueKey, ObjectKey, 和 UniqueKey。