Flutter StatefulWidget和StatelessWidget的區別和使用以及更深刻的思考

拋磚引玉

自從開始使用Flutter,接觸最多的東西確定少不了StatefulWidgetStatelessWidget。我本人在學習和了解它們的過程當中也翻閱了大量的文檔和資料,但發現他們都在講兩者的區別和使用場景以及案例——可是爲何要這麼用呢?這是一個值得思考的問題。bash

StatefulWidget和StatelessWidget簡介

免不了俗,開篇也是先講一下StatefulWidgetStatelessWidget的用法和區別吧。 Flutter中,一切皆Widget。Widget是視圖的載體,而Widget包含兩種,一種是不須要更改狀態的Widget,StatelessWidget它沒要須要管理的內部狀態,是無狀態的。另一種是可變狀態的,StatefulWidget它有須要管理的內部狀態,使用setState來管理狀態改變。 Widget是有狀態的仍是無狀態的,取決於他們依賴於狀態的變化:框架

  • 有狀態:交互或者數據改變致使Widget改變,例如改變文案
  • 無狀態:不會被改變的Widget,例如純展現頁面,數據也不會改變

特別提示

我特地用一個標題來吸引你們注意,是由於我在好幾篇博客看到了相似下面的話:less

Flutter 裏面包含兩種widget,一種是不可變的Widget——StatelessWidget,另一種是可變的Widget——StatefulWidgetide

這是大錯特錯的!!!,由於Widget只是視圖的「配置信息」,是數據的映射, Widget是不可變的,不可變的!!。變的只是Widge裏面的狀態,也就是State。 貼一段Widget源碼的截圖 佈局

注意其中的 「A widget is an immutable description of part of a user interface」。Widget只是用戶界面一部分不可變的描述——至於爲何不可變以及都不可變了還怎麼刷新UI,這兩個問題接下來我會用一片博客詳細介紹一下Flutter的渲染機制。

StatelessWidget

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,它只會在三種狀況下被調用:學習

  • 將widget插入樹中的時候,也就是第一次構建
  • 當widget的父級更改了其配置時,例如,Less的父類改變了text的值
  • 當它依賴的InheritedWidget發生變化時

StatefulWidget

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

StatefulWidget和StatelessWidget的實用場景

在涉及到Widget的工做時,遇到的頭等大事就是肯定widget應該使用StatefulWidget仍是StatelessWidget? 簡單的說,若是不須要本身維持狀態就使用StatelessWidget,不然使用StatefulWidget。 進一步分析,根據上文的介紹,咱們不難發現:this

  • 若是用戶交互或數據改變致使widget改變,那麼它就是有狀態的。
  • 若是一個widget是最終的或不可變的,那麼它就是無狀態。

而狀態的管理可能有三種方式——本身管理,父widget管理以及二者混合搭配。咱們能夠參考下面的規則選擇Widget:

  • 若是widget的狀態取決於動做,那麼最好是由widget自身來管理狀態,也就是使用StatefulWidget,例如動畫;
  • 若是狀態是用戶數據,則最好用父widget管理,也便是使用StatelessWidget,例如一個列表單個Item的選中狀態;
  • 若是仍是搖擺不定,別問,問就是StatelessWidget

更進一步的思考

前面咱們已經知道了,Widget是不可變的,若是要改變就要從新建立。而StatefulWidget使用State來經過控制自身狀態來爲本身標記狀態,這樣就能夠在下一次系統重繪檢查時從新建立。

爲何要選用StatelessWidget

經過上面的介紹,你們不難發現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會調用elementmarkNeedsBuild方法,用來標記當前element爲dirty狀態,也就是須要build。並執行BuildOwnerscheduleBuildFor方法,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的按鈕刷新頁面,日誌輸出以下:

哈哈哈,發現了一個StatelessWidget的用處了。由於StatelessWidget沒有setState方法,全部它能夠強制的減小開發者濫用setState,致使過多的頁面被刷新。官方也是推薦首選使用StatelessWidget,也就是說要減小頁面刷新的區域和層級!!! 好失望了,難道設計出StatelessWidget就是爲了規範開發者的行爲嗎????

我編不下去了啊!!!翻了翻兩個widget的build源碼,除了一個多了個state以外,我也沒發現什麼端倪。 總之:

  • 優先使用StatelessWidget
  • 含有大量子Widget(如根佈局、次根佈局)最好使用StatelessWidget
  • StatefulWidget最好用在子節點,同時儘可能減小它的子節點。

總結

開篇拋出的問題我仍是沒有完全想明白:

相關文章
相關標籤/搜索