【Flutter 基礎】State x Widget

林小帥發表於2021-07-19

注:本文從個人公眾號(島前嶼端)中遷移重新發布

Flutter 是谷歌的移動 UI 框架,可以從單個程式碼庫快速的為移動端(iOS & Android)、Web、桌面端、嵌入式裝置上構建高質量的原生使用者介面和應用程式。


上一篇中我們主要說一下簡單的 Hello World 是如何實現的。那麼這次我就來說說,關於元件的兩個形式。

但是在此之前,我覺得還是有必要簡單的來說一說關於 Dart 語言的一些東西。

關於 Dart

Dart 語言是由谷歌公司開發的網路程式語言,於2011年10月10日釋出。Dart是一個小型的編譯型語言

Dart 主要支援和麵向 3 個不同平臺的開發需求:

  • Dart webdev 開發瀏覽器應用。
  • Flutter 開發移動應用。
  • Dart VM 開發指令碼或者伺服器應用。

Dart 的特性

Dart 吸收了一些後端語言和前端語言的一部分特點。在 Dart 中所有的東西都是物件,function 是物件;String 是物件;Number是物件;null 也是物件。(是不是有內味兒了?)

  • 這是一個強型別的語言,可以做型別推斷。
  • 一切資料型別都派生自 Object
  • 若無初始化的變數均為 null 預設值
  • 擁有動態型別、泛型以及運算子過載。
  • 擁有頂級函式 main。
  • 沒有宣告類關鍵字 "public" "private",以 "_" 開頭則表示僅對當前域有效,既是私有的。
  • final 單次宣告,既只可賦值一次,之後再次賦值無效。

Ok,關於 Dart 語言就簡單介紹這點好了。更多的 Dart 語言內容請參考:dart.dev/

State x Widget 繼承關係

在簡單介紹關於 Dart 語言後,就可以說說 StatelessWidgetStatefulWidget 了。上一篇中說到 StatelessWidget 和 StatefulWidget 均繼承自 Widget,都是 Widget 的抽象類。【傳送門】

image.png
(StatelessWidget 的繼承關係)

image.png
(StatefulWidget 的繼承關係)

從原始碼中我們可以看出且證實了 StatelessWidgetStatefulWidget繼承自 Widget 且都是 Widget 的抽象類 abstract。

這裡我將他們的關係整理了一下。

image.png
(StatelessWidget 和 StatefulWidget 的繼承關係圖)

如圖所示,而這也從側面證實了 Dart 中所有物件都是 Object

在之前的文章中我也有提到過 StatelessWidgetStatefulWidget 雖然都是 Widget 的抽象類。但是他們之間還是有區別的,它們分別用於實現不同場景下使用的 Widget 元件

State x Widget 有狀態與無狀態

StatelessWidget - 無狀態

StatelessWidget 是無狀態的,意味著無法通過資料變更而更新。

這樣,我們來實現一個小?,你就知道了。

專案依然是我們的 MyApp,我們要先 實現一個 名叫 UnchangeWidget 的類,讓它 繼承於 StatelessWidget

再來實現一個按鈕,通過按鈕的點選事件來更新我們的文字內容

// 繼承 StatelessWidget 意味著無法通過資料變更而改變內容
class UnchangeWidget extends StatelessWidget {
  String setText = '我是原來的文字'; // 宣告變數文字 - 預設值

  @override
  Widget build(BuildContext context) {
    return Container(
        child: Column(
      children: <Widget>[
        Text(setText), // 使用變數 setText 來設定 Text 的內容
        RaisedButton(
          child: Text('改變內容'),
          onPressed: () { // 按鈕點選事件 - 改變內容
            print('觸發了按鈕');
            setText = '我是新的文字'; // 賦值新文字內容
          },
        )
      ],
    ));
  }
}
複製程式碼

結果如下:

image.png
(文字內容並沒有更新)

即使觸發了按鈕,且對 setText 進行了賦值,但是我們的 Text 內容依然沒有更新。

StatefulWidget - 有狀態

StatefulWidget 是有狀態的,意味著可以通過資料變更而更新,需要通過 setState 來管理狀態。

Ok,那麼我們再來改寫一下這個 UnchangeWidget ,將它改寫為 繼承於 StatefulWidget

這裡可以通過編輯器的快速修復功能來進行程式碼的改寫。

// 繼承 StatefulWidget 意味著可以通過資料變更而改變內容
class UnchangeWidget extends StatefulWidget {
  String setText = '原來的內容'; // 變數設定文字 - 預設值

  @override // 建立狀態控制類的簡寫方式,這裡的 new 也可以省略
  _UnchangeWidgetState createState() => _UnchangeWidgetState();
}

class _UnchangeWidgetState extends State<UnchangeWidget> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(children: <Widget>[
        Text(widget.setText), // 使用變數 setText 來設定 Text 的內容
        RaisedButton(
          child: Text('改變內容'),
          onPressed: () { // 按鈕點選事件 - 改變內容
            print('觸發了按鈕');
            setState(() { // 呼叫狀態變更函式
              widget.setText = '我是新的文字'; // 賦值新文字內容
              print('狀態變更文字更新');
            });
          },
        ),
      ]),
    );
  }
}
複製程式碼

結果如下:

image.png
(使用 setState 更新內容)

這裡我們可以從結果中看到,通過按鈕的觸發呼叫 setState 來進行狀態的更新從而更新了 Text 的文字內容。

疑問

這時候一些少俠們就不樂意了:“StatefulWidget 用了 setState 來更新內容,StatelessWidget 沒有,不公平!

640.gif

哈哈哈哈,沒關係,那麼我們就來試一試!我就喜歡你這種敢於發出不同聲音的人。

我們現在繼續使用 StatefulWidget 的程式碼,然後直接將繼承物件更新為 StatelessWidget

image.png

(更改繼承型別為 StatelessWidget)

那麼我們來看看結果會是怎麼樣?在命令列中鍵入 R 後耐心等待……

image.png

來來來,讓我們掌聲送給這幾位勇敢的少俠,喜提滿屏紅。

為什麼會這樣?

為什麼會這樣呢?因為它們在原始碼中的實現是不同的。

image.png
(StatelessWidget 原始碼部分)

image.png
image.png
(StatefulWidget 原始碼部分)

在原始碼中 StatelessWidgetStatefulWidget 的實現方式不一樣。

我可以簡單翻譯一下。

StatelessWidget 部分:

此方法的實現必須僅依賴於:

  • widget 的欄位,它們本身不能隨著時間進行改變,以及任何從 [context] 環境中使用的狀態。

如果 widget 的 [build] 方法依賴於其他的東西,那麼就請改為使用 [StatefulWidget]。

StatefulWidget 部分:

子類應該重寫此方法以返回其關聯的新建立的[State]子類例項。
(1)在構建 widget 時,可以同步獲取狀態資訊。
(2)在 widget 的生命週期內,widget 的實現需要確保在狀態發生改變時使用 [State.setState] 來及時通知 [State] 需要更改的資訊。

所以由此可知 StatelessWidget 是不參與元件生命 State 狀態管理的,亦沒有對 State 的關聯操作,而 StatefulWidget 則是參與實現了 State 的重寫,亦要求使用者重寫 createState 以保證對 State 的關聯

就是說如果你想實現一個動態的可更新資料的元件就使用 StatefulWidget,如果只是想展示靜態的文字資訊或內容就使用 StatelessWidget


總結

  • 在還不太理解的情況下,可以多去嘗試以驗證你的想法。
  • 有時候可以考慮看看原始碼,一些優秀的程式語言或者開源框架在原始碼部分都會有良好的註釋內容,它會告訴你它們在這裡做了些什麼。

相關文章