衆所周知,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一下吧