Flutter开发中避免构建Widget的终极解决方案
Flutter是一个基于React框架概念开发的框架。有很多相似之处,但也有细微的差别。我感触比较深的是flutter的重建。那么有没有办法阻止恢复呢?有!
在小部件前面添加 const
是可以的,一劳永逸,但是当您添加 const 时,您的小部件永远不会更新。如果你不是在写静态页面,最好不要使用它。
将您的元素编写为“叶子”元素
请参阅 flutter 文档并将所有元素定义为叶子(树的最低级别),然后更改叶子元素内部的属性以防止叶子折叠。相互撞击。 ,呃,在我看来,这和人,如果调用setState,所有组合值都会被重置。是的,我就是用这个思路解决了原来的重建问题。例如,使用StreamBuilder
可以加载您的组件,然后使用流来触发StreamBuilder的内部构建,并使用StreamBuilder来分离外部组件。以这种方式编写有一些缺点。我必须编写一个额外的流并关闭该流,这确实毫无意义。
使用其他库,比如Provider
可以看到Provider库的作者提供了一些widget来减少配置,但感觉不是很简单易用。这些库的实现与 StreamBuilder 类似。使用小部件来区分其他小部件并限制其中的更新,但它们都有一个共同点。需要使用额外的外部变量来更新内部
终极方法
用过react的人都知道,react类组件有一个非常重要的生命周期,叫做shouldComponentUpdate
。我们可以在组件内部重写这个声明周期来提高性能。
最好的方法是比较单元的新旧部分的值,看看它们是否匹配。如果它们匹配,则无需更新该项目。 Flutter有同样的生命周期吗?不!
flutter团队认为flutter的速度非常快,并且flutter有类似反馈的diff算法来比较组件是否需要更新。他们做了优化和缓存,因为更新 flutter 组件是一项非常昂贵的任务。 ,而重建widget只是创建了一个新的widget实例,就像执行一段dart代码一样,没有对UI层进行任何更改,而且它还对新旧widget进行了差异,使用diff widgets来减少。对元素的需求。任何改变,只要不导致组件被破坏或重建,都不会影响整体性能。
但是通过谷歌和百度,你仍然可以找到人们在寻找防止重制的方法,这说明市场还是有需求的。我个人认为这并不算过度优化。事实上,还有一些需要改进的地方。比如Google推荐的状态管理库Provider,提供了一种减少不必要配置的方法
(我)不要(想)太多(付出)说(笑):
library should_rebuild_widget;
import 'package:flutter/material.dart';
typedef ShouldRebuildFunction<T> = bool Function(T oldWidget, T newWidget);
class ShouldRebuild<T extends Widget> extends StatefulWidget {
final T child;
final ShouldRebuildFunction<T> shouldRebuild;
ShouldRebuild({@required this.child, this.shouldRebuild}):assert((){
if(child == null){
throw FlutterError.fromParts(
<DiagnosticsNode>[
ErrorSummary('ShouldRebuild widget: builder must be not null')]
);
}
return true;
}());
@override
_ShouldRebuildState createState() => _ShouldRebuildState<T>();
}
class _ShouldRebuildState<T extends Widget> extends State<ShouldRebuild> {
@override
ShouldRebuild<T> get widget => super.widget;
T oldWidget;
@override
Widget build(BuildContext context) {
final T newWidget = widget.child;
if (this.oldWidget == null || (widget.shouldRebuild == null ? true : widget.shouldRebuild(oldWidget, newWidget))) {
this.oldWidget = newWidget;
}
return oldWidget;
}
}
复制代码
这几行代码,不到40个代码,我们看一下测试代码:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:should_rebuild_widget/should_rebuild_widget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Test(),
);
}
}
class Test extends StatefulWidget {
@override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
int productNum = 0;
int counter = 0;
_incrementCounter(){
setState(() {
++counter;
});
}
_incrementProduct(){
setState(() {
++productNum;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
constraints: BoxConstraints.expand(),
child: Column(
children: <Widget>[
ShouldRebuild<Counter>(
shouldRebuild: (oldWidget, newWidget) => oldWidget.counter != newWidget.counter,
child: Counter(counter: counter,onClick: _incrementCounter,title: '我是优化过的Counter',) ,
),
Counter(
counter: counter,onClick: _incrementCounter,title: '我是未优化过的Counter',
),
Text('productNum = $productNum',style: TextStyle(fontSize: 22,color: Colors.deepOrange),),
RaisedButton(
onPressed: _incrementProduct,
child: Text('increment Product'),
)
],
),
),
),
);
}
}
class Counter extends StatelessWidget {
final VoidCallback onClick;
final int counter;
final String title;
Counter({this.counter,this.onClick,this.title});
@override
Widget build(BuildContext context) {
Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);
return AnimatedContainer(
duration: Duration(milliseconds: 500),
color:color,
height: 150,
child:Column(
children: <Widget>[
Text(title,style: TextStyle(fontSize: 30),),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('counter = ${this.counter}',style: TextStyle(fontSize: 43,color: Colors.white),),
],
),
RaisedButton(
color: color,
textColor: Colors.white,
elevation: 20,
onPressed: onClick,
child: Text('increment Counter'),
),
],
),
);
}
}
复制代码
布局翻译:
- 我们已经定义了Counter元素,并且Counter在构造阶段会改变它的颜色。每次执行构建时,背景颜色都会随机生成,以便我们检查项目是否已构建。此外,计数器还从父元素接收计数器值并显示它。它还得到一个标题来区分不同的Counter名称
- 看这里的代码
Column(
children: <Widget>[
ShouldRebuild<Counter>(
shouldRebuild: (oldWidget, newWidget) => oldWidget.counter != newWidget.counter,
child: Counter(counter: counter,onClick: _incrementCounter,title: '我是优化过的Counter',),
),
Counter(
counter: counter,onClick: _incrementCounter,title: '我是未优化过的Counter',
),
Text('productNum = $productNum',style: TextStyle(fontSize: 22,color: Colors.deepOrange),),
RaisedButton(
onPressed: _incrementProduct,
child: Text('increment Product'),
)
],
)
复制代码
上面的Counter是用ShouldRebuild包装的,发送shouldRebuild参数是因为指定的条件是如果没有收到这个counter就构建。计数器不兼容。如果比较新旧计数器并发现计数器匹配,则不会重建它们。以下计数器未优化。
- 当点击按钮添加Product
增量Product
时,会导致productNum增加,但计数器目前并没有增量,所以ShouldRebuild填充的Counter并没有重建,下面的Unwrapped容器是弥补的。看gif:

声明的规则
实际上,规则和用const声明的widget的规则是一样的。我们来看看flutter源码
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner._debugElementWasRebuilt(child);
return true;
}());
return child;
}
...
}
复制代码
去掉它的部分。第一个
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
return child;
}
复制代码
这是关键。 Flutter 检测到 child.widget 也是,即旧的 widget 和新的 widget 是一样的。如果引用匹配,则直接返回孩子。一旦完成此过程,设置过程就完成了。请看看它做了什么
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
复制代码
当你看到rebuild()时,你就知道你必须执行构建。
其实,检查 if (child.widget == newWidget) 之后,我们也知道为什么 Text() 会阻止文本被多次创建,因为常量永远不会改变
总结
在这个 Widget 中我们可以在主元素中设置状态,然后将页面分成几个子元素,然后用ShouldRebuild附加子元素,并设置一个构建条件以防止不必要的重建。您可以根据需要设置State。当然,如果你的状态被多个组件使用,你就需要状态管理。然而,有些人可能认为这太过分了。我个人认为是否需要改进取决于你的具体情况。如果有一天用户反映你的页面卡住了,你需要改进,或者你觉得配置对你产生了影响。动画等功能会重复执行,因此需要避免重建它们。
github:shouldRebuild
作者:超人一路向北
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。