衆所周知,flutter是借鑑了前端框架React的思想而開發的框架,有不少類似之處,也有看不到的不同,我目前感覺最深的就是flutter無所不在的rebuild,那麼有辦法阻止rebuild嗎?html
這個辦法確實能夠,一勞永逸,可是你一旦加了const,你這個widget就永遠不會更新了,除非你是在寫靜態頁面,不然你最好不要用它前端
參考flutter文檔
就是把那你的組件都定義成葉子,樹的最底層,而後你在葉子組件內部更改狀態,這樣葉子之間互不影響,emm,在我看來這樣子跟react的狀態提高的思想相反了,由於你爲了互不影響,你不能把狀態放到根節點,放到根節點,一調用setState那所有自組價就rebuild了,我一開始一直是用這個思路來解決rebuild的問題的,
好比使用StreamBuilder
這個能夠包裹你的組件,而後用流來觸發StreamBuilder內部rebuild,經過StreamBuilder來隔絕外面的組件,這樣寫有個小缺點,我要額外寫個流,還要關閉流,很囉嗦。react
這些庫的實現方法跟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,由於常量是一直不會變的
若是以爲幫助到了你,請star一下吧