自從開始使用Flutter,接觸最多的東西確定少不了StatefulWidget
和StatelessWidget
。我本人在學習和了解它們的過程當中也翻閱了大量的文檔和資料,但發現他們都在講兩者的區別和使用場景以及案例——可是爲何要這麼用呢?這是一個值得思考的問題。bash
免不了俗,開篇也是先講一下StatefulWidget
和StatelessWidget
的用法和區別吧。 Flutter中,一切皆Widget。Widget是視圖的載體,而Widget包含兩種,一種是不須要更改狀態的Widget,StatelessWidget
它沒要須要管理的內部狀態,是無狀態的。另一種是可變狀態的,StatefulWidget
它有須要管理的內部狀態,使用setState
來管理狀態改變。 Widget是有狀態的仍是無狀態的,取決於他們依賴於狀態的變化:框架
我特地用一個標題來吸引你們注意,是由於我在好幾篇博客看到了相似下面的話:less
Flutter 裏面包含兩種widget,一種是不可變的Widget——StatelessWidget,另一種是可變的Widget——
StatefulWidget
ide
這是大錯特錯的!!!,由於Widget只是視圖的「配置信息」,是數據的映射, Widget
是不可變的,不可變的!!。變的只是Widge裏面的狀態,也就是State。 貼一段Widget
源碼的截圖 佈局
「A widget is an immutable description of part of a user interface」
。Widget只是用戶界面一部分不可變的描述——至於爲何不可變以及都不可變了還怎麼刷新UI,這兩個問題接下來我會用一片博客詳細介紹一下Flutter的渲染機制。
StatelessWidget是一個沒有狀態的widget——沒有要管理的內部狀態。它經過構建一系列其餘小部件來更加具體地描述用戶界面,從而描述用戶界面的一部分。當咱們的頁面不依賴Widget對象自己中的配置信息以及BuildContext時,就能夠用到無狀態組件。例如當咱們只須要顯示一段文字時。實際上Icon、Divider、Dialog、Text等都是StatelessWidget的子類。 StatelessWidget
的基本使用以下:post
class Less extends StatelessWidget {
final String text;
const Less({Key key, this.text}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Text(text);
}
}
複製代碼
Less
包含了一個從外部接受一個不可變的數據源text並將它顯示。 無狀態的組件的聲明週期只有一個:build
,它只會在三種狀況下被調用:學習
StatefulWidget是可變狀態的widget。使用setState方法管理StatefulWidget的狀態的改變。調用setState通知Flutter框架某個狀態發生了變化,Flutter會從新運行build方法,應用程序變能夠顯示最新的狀態。 狀態是在構建widget的時候,widget能夠同步讀取的信息,而這些狀態會發生變化。要確保在狀態改變的時候即便通知widget進行動態更改,就須要用到StatefulWidget
。例如一個計數器,咱們點擊按鈕就要讓數字加一。在Flutter中,Checkbox、FadeImage等都是有狀態組件。 StatefulWidget
的基本使用以下:動畫
class Full extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _Full();
}
}
class _Full extends State<Full> {
int count = 0;
@override
Widget build(BuildContext context) {
// TODO: implement build
return new GestureDetector(
onTap: onClick,
child: new Text("$count"),
);
}
void onClick() {
setState(() {
count += 1;
});
}
}
複製代碼
Full
包含了一個內部持有的int狀態,每次點擊自增一,平使用setState刷新頁面顯示最新的值。 StatefulWidget
的生命週期比較複雜,有興趣的能夠去看個人另外一篇博客:Flutter視圖Widget生命週期ui
在涉及到Widget的工做時,遇到的頭等大事就是肯定widget應該使用StatefulWidget仍是StatelessWidget? 簡單的說,若是不須要本身維持狀態就使用StatelessWidget
,不然使用StatefulWidget
。 進一步分析,根據上文的介紹,咱們不難發現:this
而狀態的管理可能有三種方式——本身管理,父widget管理以及二者混合搭配。咱們能夠參考下面的規則選擇Widget:
StatefulWidget
,例如動畫;StatelessWidget
,例如一個列表單個Item的選中狀態;StatelessWidget
。前面咱們已經知道了,Widget是不可變的,若是要改變就要從新建立。而StatefulWidget使用State來經過控制自身狀態來爲本身標記狀態,這樣就能夠在下一次系統重繪檢查時從新建立。
經過上面的介紹,你們不難發現StatefulWidget幾乎是這樣一個存在——我在任何需求下使用它都能實現想要的效果,那麼咱們爲何不一股腦所有使用它呢?既然它也能實現StatelessWidget的效果,那咱們還要StatelessWidget作什麼?StatefulWidget就是一個全能的存在啊!! 爲了解釋這個疑問,咱們就要去了解一下StatefulWidget伴隨着全能而來的代價! 首先咱們粗略的追溯一下setState的刷新源碼:
@protected
void setState(VoidCallback fn) {
...
_element.markNeedsBuild();
}
void markNeedsBuild() {
...
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
void scheduleBuildFor(Element element) {
...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
複製代碼
去掉全部的assert校驗,只保留關鍵代碼,咱們發現setState會調用element
的markNeedsBuild
方法,用來標記當前element
爲dirty狀態,也就是須要build。並執行BuildOwner
的scheduleBuildFor
方法,BuildOwner
是負責管理element
的。直接追溯到onBuildScheduled
,該發放的實現爲widget/binding.dart
中的_handleBuildScheduled
方法,其中調用了scheduler/binding.dart
中的ensureVisualUpdate
,最後調用了scheduleFrame
方法:
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
assert(() {
if (debugPrintScheduleFrameStacks)
debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
return true;
}());
window.scheduleFrame();
_hasScheduledFrame = true;
}
複製代碼
關鍵的代碼出現了:window.scheduleFrame()
這是一個Native方法:
實際上setState只是用來標記state對象須要根據已經變動的狀態從新build來建立新的widget。調用setState將會出發每一個子Widget的構造方法以及build方法。這意味着若是根佈局是一個StatefulWidget
,那麼setState以後,整個頁面全部的widget都會重建。 經過代碼來驗證一下:
class FulBackPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _FulBackPage();
}
}
class _FulBackPage extends State<FulBackPage> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Column(
children: <Widget>[
Full(name: "A"),
Full(name: "B"),
Full(name: "C"),
Full(name: "D"),
Less(name: "E"),
GestureDetector(
onTap: () {
setState(() {});
},
child: Text("點擊"),
)
],
);
}
}
class Full extends StatefulWidget {
final String name;
Full({Key key, this.name}) : super(key: key) {
print("有狀態組件$name:建立了");
}
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _Full();
}
}
class _Full extends State<Full> {
@override
Widget build(BuildContext context) {
print("有狀態組件${widget.name}:build了");
return new GestureDetector(
onTap: () {
setState(() {});
},
child: new Text(widget.name),
);
}
}
class Less extends StatelessWidget {
final String name;
Less({Key key, this.name}) : super(key: key){
print("無狀態組件$name:建立了");
}
@override
Widget build(BuildContext context) {
// TODO: implement build
print("無狀態組件$name:build了");
return new Text(name);
}
}
複製代碼
每次點擊FulBackPage
的按鈕刷新頁面,日誌輸出以下:
我編不下去了啊!!!翻了翻兩個widget的build源碼,除了一個多了個state以外,我也沒發現什麼端倪。 總之:
開篇拋出的問題我仍是沒有完全想明白: