众所周知,flutter是借鉴了前端框架React的思想而开发的框架,有不少类似之处,也有看不到的不同,我目前感觉最深的就是flutter无所不在的rebuild,那么有办法阻止rebuild吗?有!html
这个办法确实能够,一劳永逸,可是你一旦加了const,你这个widget就永远不会更新了,除非你是在写静态页面,不然你最好不要用它前端
参考flutter文档 就是把那你的组件都定义成叶子,树的最底层,而后你在叶子组件内部更改状态,这样叶子之间互不影响,emm,在我看来这样子跟react的状态提高的思想相反了,由于你为了互不影响,你不能把状态放到根节点,放到根节点,一调用setState那所有自组价就rebuild了,我一开始一直是用这个思路来解决rebuild的问题的, 好比使用StreamBuilder
这个能够包裹你的组件,而后用流来触发StreamBuilder内部rebuild,经过StreamBuilder来隔绝外面的组件,这样写有个小缺点,我要额外写个流,还要关闭流,很啰嗦。react
你能够看到Provider库的做者提供了一些Widget来减小rebuild,可是我感受都不太简洁,易用 这些库的实现方法跟StreamBuilder差很少,都是经过一个Widget来隔绝其余Widget,让更新限制在内部,可是都有一个共同点,你要配合额外的外部变量去触发内部的更新git
用过react的人都知道,react的类组件有个很重要的生命周期叫shouldComponentUpdate
,咱们能够在组件内部重写这个声明周期来进行性能优化。github
如何优化呢,就是对比组件的新旧props的属性的值是否一致,若是一致那组件就不必更新. 那flutter有没有相似的生命周期呢?没有!算法
flutter团队认为flutter的渲染速度已经够快了,而且flutter实际也有相似react 的diff算法来对比element是否须要更新,他们作了优化和缓存,由于更新flutter的element是很昂贵的操做,而rebuild Widget只是从新new 了一个widget的实例,就像只是执行了一段dart代码同样,没涉及到任何ui层的更改,并且他们也对新旧widget作了diff,经过diff widget来减小对element层的更改,无论怎样,只要没有致使element销毁,重建,通常不会影响什么性能。api
可是经过谷歌和百度你仍是能发现有人在搜索如何防止rebuild,这说明了市场仍是有需求的。我我的认为,这个不叫过分优化,实际上是有这个场景须要优化的,好比谷歌推荐的状态管理库Provider就提供了如何减小没必要要的rebuild的方法缓存
话(我)不(想)多(吐)说(槽)了:性能优化
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'),
),
],
),
);
}
}
复制代码
布局效果图:
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接收的counter不一致时才rebuild,若是新老Counter对比发现counter一致那就不rebuild, 而下面的Counter则没有作优化。
increment Product
,会触发增长productNum,而此时没有增长counter,因此被ShouldRebuild包裹的Counter并无rebuild,而下面没有包裹的Counter就rebuild了 来看下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是同一个,引用一致的话就直接返回了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;
}
复制代码
这里若是能够更新,就会走child.update(),这个方法一旦走了,那build方法确定会执行了。 请看它作了什么事
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
复制代码
看到rebuild()就知道必定去执行build了。
其实看到 if (child.widget == newWidget) 咱们也知道为何 const Text()会让Text不会重复build,由于常量是一直不会变的
有了这个Widget咱们能够把状态都放在根组件,而后把页面拆分红多个子组件,而后用ShouldRebuild包裹子组件,同时设置rebuild的条件就能够阻止 没必要要的 重渲染。你能够尽情的setState了,固然若是你的状态被多个组件使用,这时候你就须要状态管理了。 可是,可能有人会以为是否过分优化,我我的以为是否须要优化是根据你本身的状况定的,若是某天用户反馈你的页面卡顿,那你就须要优化,又或者你以为rebuild影响到了你的功能,好比动画重复执行了,那你就须要阻止rebuild了。
若是以为帮助到了你,请star一下吧