Flutter系列四:你真的了解StatelessWidget和StatefulWidget的區(qū)別嗎潮尝?

開發(fā)者在進(jìn)行Flutter開發(fā)時少孝,大部分工作基本上少不了與StatelessWidgetStatefulWidget打交道邑退。大家是否真的了解StatelessWidgetStatefulWidget?

討論

我閱讀了很多網(wǎng)上的文章,大部分會講解兩者的使用上的區(qū)別劳澄,一部分文章有解釋這兩者的區(qū)別瓜饥。但是他們的解釋有的是字面解釋,有的是淺嘗輒止浴骂,有的甚至是有一定的誤導(dǎo)乓土。

列出網(wǎng)上一些文章中的解釋:

  1. 如果我們的Widget是StatelessWidget,那么當(dāng)他的內(nèi)容被創(chuàng)建出來之后溯警,就不能再改變了趣苏。相反StatefulWidget就可以。
  2. 無狀態(tài)Widget梯轻,就是說一旦這個Widget創(chuàng)建完成食磕,狀態(tài)就不允許再變動。有狀態(tài)Widget喳挑,就是說當(dāng)前Widget創(chuàng)建完成之后彬伦,還可以對當(dāng)前Widget做更改,可以通過setState函數(shù)來刷新當(dāng)前Widget來達(dá)到有狀態(tài)伊诵。
  3. StatelessWidget是一個不需要狀態(tài)更改的widget单绑,它沒有要管理的內(nèi)部狀態(tài)。StatefulWidget是可變狀態(tài)的widget曹宴。

如果你對上述一些觀點很認(rèn)同的話搂橙,我覺得閱讀本篇文章應(yīng)該可以給你提供一個不一樣的理解視角。

Widget

我們要比較StatelessWidgetStatefulWidget的區(qū)別笛坦,我們得先知道什么是Widget区转。

官方對Widget的解釋是:

A widget is an immutable description of a part of a user interface.

Widget是部分界面的不可變的描述信息

重要的事情說三遍:

Widget不可變的;

Widget不可變的;

Widget不可變的版扩。

我們從代碼上看看Widget如何實現(xiàn)的不可變废离。Widget的代碼如下:

@immutable
abstract class Widget extends DiagnosticableTree {
  
  const Widget({ this.key });

  final Key? key;
  
  // 省略...
}

我們可以看到Widget左上角有一個@immutable注解,這個注解的意思是所有的屬性必須是final修飾礁芦,也就是Widget一旦初始化以后蜻韭,其屬性將不可變。

接下來我們再看看StatelessWidgetStatefulWidget的官方解釋和相關(guān)代碼:

StatelessWidget --- A widget that does not require mutable state.

abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key? key }) : super(key: key);

  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

StatefulWidget --- A widget that has mutable state.

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key? key }) : super(key: key);

  @override
  StatefulElement createElement() => StatefulElement(this);

  @protected
  @factory
  State createState();
}
Widget總結(jié)
  1. StatelessWidgetStatefulWidget沒有本質(zhì)區(qū)別宴偿,他們的所有屬性都是不可變的湘捎。它們都沒法更新,除非用一個新的Widget去替換它們窄刘。
  2. StatefulWidget擁有一個可變的State窥妇。

這樣我們就得到了一個結(jié)論:StatelessWidgetStatefulWidget的區(qū)別就在這個可變的State了。

新的問題又來了娩践,這個State扮演了什么作用呢活翩?

State

我們進(jìn)行界面的修改烹骨,一般會調(diào)用state.setState()方法。那這個方法是如何實現(xiàn)界面元素修改的呢材泄?

void setState(VoidCallback fn) {
    final dynamic result = fn() as dynamic;
    _element!.markNeedsBuild();
  }

setState方法很簡單:

  1. 執(zhí)行傳入的函數(shù);
  2. _element調(diào)用了markNeedsBuild方法沮焕。
void markNeedsBuild() {
    if (dirty)
      return;
    _dirty = true;
    owner!.scheduleBuildFor(this);
}
  1. _element把自己的_dirty屬性設(shè)置為true;
  2. BuildOwner調(diào)用scheduleBuildFor方法。
void scheduleBuildFor(Element element) {
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      // 1
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
  }
  1. BuildOwner調(diào)用onBuildScheduled方法拉宗;

內(nèi)容回顧:onBuildScheduled方法是在WidgetsBindinginitInstances中初始化的,一系列調(diào)用后最后調(diào)用的就是scheduleFrame請求Native Platform要刷新界面峦树。

  1. element加入到dirtyElements中。

在合適的時候Flutter Engine會回調(diào)SchedulerBindinghandleDrawFrame方法旦事,最后會調(diào)用BuildOwnerbuildScope方法魁巩。

void buildScope(Element context, [ VoidCallback? callback ]) {
    int dirtyCount = _dirtyElements.length;
    int index = 0;
    while (index < dirtyCount) {
        _dirtyElements[index].rebuild();
    }
}

遍歷dirtyElements元素,每個element調(diào)用rebuild姐浮。

rebuild的作用是什么谷遂?沒錯,就是我們開頭提到的對界面元素進(jìn)行更新的操作卖鲤。

刷新渲染的具體邏輯肾扰,我將會在后續(xù)文章中詳細(xì)介紹,這里沒法詳細(xì)展開蛋逾。

結(jié)論

StatelessWidgetStatefulWidget的本質(zhì)區(qū)別就是能否自我重新構(gòu)建(self rebuild)集晚。

區(qū)別

一些思考

  • 既然StatefulWidget的主要作用只是為了賦予了其自我重新構(gòu)建(self rebuild)的能力,那為什么需要State呢换怖?

Widget依賴于構(gòu)造函數(shù)Build方法中的BuildContext中的外部信息甩恼,如果是外部觸發(fā)的Build(例如:祖先Widget build),所有信息都是完整的沉颂。如果self rebuild則無法獲取更新后的外部信息,所以需要內(nèi)部維護(hù)一份不依賴于外部的信息悦污,State就是這個作用铸屉。

  • 既然StatefulWidget的功能更完善,為什么又提供一個StatelessWidget呢切端?

這個問題其實等同于為什么官方要限制我們使用self rebuild彻坛?每次Build都需要新建和銷毀大量的WidgetElement Treediff踏枣,甚至繁重的渲染和重繪昌屉。官方推薦使用StatelessWidget,其實就是為了性能的考慮而對開發(fā)者進(jìn)行的一些約束茵瀑,限制開發(fā)者無節(jié)制的使用self rebuild造成的性能降低间驮。

  • 可不可以在開發(fā)中全部都使用StatefulWidget

當(dāng)然可以马昨,但是不推薦竞帽,理由見上個問題扛施。

  • 可不可以在開發(fā)中全部都使用StatelessWidget

如果是顯示簡單的不變的內(nèi)容可以這樣使用屹篓,但是這種場景太少了疙渣。至少在App應(yīng)用中不太可能。

  • 開發(fā)中如何選擇StatelessWidget還是StatefulWidget堆巧?

首選StatelessWidget妄荔,當(dāng)無法滿足需求的時候用VS Code或者Android Stutio的快捷鍵將其變成StatefulWidget

實戰(zhàn)分享

我們前面比較了StatelessWidgetStatefulWidget的區(qū)別谍肤,進(jìn)行了一些分析懦冰,到底如何寫出更好更優(yōu)化的代碼,現(xiàn)在我們就用Flutter官方的計數(shù)器Demo來練練手谣沸。

Counter

通過前面的分析刷钢,我們知道點擊FloatingActionButton會調(diào)用_MyHomePageStatesetState進(jìn)行rebuild。如下圖所示:

demo_build

細(xì)心的你可能發(fā)現(xiàn)問題了乳附,我只是想修改Scaffold->Body->Center->Column->第二個Text中的文字内地。而Build的起點是Scaffold,這么長的構(gòu)建鏈條相當(dāng)于修改一個文字赋除,把整個頁面都重新構(gòu)建了一次阱缓。就顯然是一個無法忽視的問題。

注意:真實的Build鏈條不是我上面列的這么短举农,因為Scaffold等都進(jìn)行了封裝荆针,真實的Build得進(jìn)入他們的build方法去了解,真實的Build鏈條比我們代碼中看到的Scaffold->Body->Center->Column->第二個Text這個邏輯復(fù)雜多了颁糟。

修改的思路就是我們只需要在第二個Text上封裝一個StatefulWidget航背,讓這個StatefulWidgetsetState去觸發(fā)第二個Text的文字修改。

我們抽提一個CounterText

class CounterText extends StatefulWidget {

  final _CounterTextState state = _CounterTextState();

  CounterText({
    Key key,
  }) : super(key: key);

  @override
  _CounterTextState createState() => state;
}

class _CounterTextState extends State<CounterText> {

  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  } 

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(
        '$_counter',
        style: Theme.of(context).textTheme.headline4,
      ),
    );
  }
}

CounterText的使用:

  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  CounterText counterText = CounterText();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            counterText,
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counterText.state._incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }

}

這樣就改造完成了棱貌。

總結(jié):

我們需要對StatelessWidgetStatefulWidget有一個全面的了解玖媚,才能正確的使用他們。歡迎一起探討和學(xué)習(xí)婚脱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載今魔,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末障贸,一起剝皮案震驚了整個濱河市错森,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篮洁,老刑警劉巖涩维,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘀粱,居然都是意外死亡激挪,警方通過查閱死者的電腦和手機(jī)辰狡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垄分,“玉大人宛篇,你說我怎么就攤上這事”∈” “怎么了叫倍?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長豺瘤。 經(jīng)常有香客問我吆倦,道長,這世上最難降的妖魔是什么坐求? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任蚕泽,我火速辦了婚禮,結(jié)果婚禮上桥嗤,老公的妹妹穿的比我還像新娘须妻。我一直安慰自己,他們只是感情好泛领,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布荒吏。 她就那樣靜靜地躺著,像睡著了一般渊鞋。 火紅的嫁衣襯著肌膚如雪绰更。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天锡宋,我揣著相機(jī)與錄音儡湾,去河邊找鬼。 笑死员辩,一個胖子當(dāng)著我的面吹牛盒粮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奠滑,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妒穴!你這毒婦竟也來了宋税?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤讼油,失蹤者是張志新(化名)和其女友劉穎杰赛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矮台,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乏屯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年根时,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辰晕。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛤迎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出含友,到底是詐尸還是另有隱情替裆,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布窘问,位于F島的核電站辆童,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惠赫。R本人自食惡果不足惜把鉴,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望儿咱。 院中可真熱鬧庭砍,春花似錦、人聲如沸概疆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岔冀。三九已至凯旭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間使套,已是汗流浹背罐呼。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留侦高,地道東北人嫉柴。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像奉呛,于是被迫代替她去往敵國和親计螺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容