flutter系列之:用來管理複雜狀態的State詳解

簡介

Flutter的基礎是widget,根據是否需要跟用戶進行交互,widget則可以分為StatelessWidget和StatefulWidget。StatelessWidget只能根據傳入的狀態進行簡單的初始化widget,如果要實現跟用戶交互這種複雜的功能,則需要用到StatefulWidget。

但是對於StatefulWidget本身來說,它並不存儲任何狀態,所有的狀態都是存放在和StatefulWidget關聯的State中的。

今天,讓我們來探索一下StatefulWidget和State的關係。

StatefuWidget和State

StatefulWidget的定義很簡單,它是一個abstract class,繼承它只需要實現一個createState的方法:

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

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

  @protected
  @factory
  State createState(); 
}

注意,這裡的createState是一個工廠類方法。這就意味着一個StatefulWidget可以創建多個State。

比如,如果從樹中刪除一個StatefulWidget,稍後再次將其插入到樹中,Flutter將再次調用StatefulWidget.createState 來創建一個新的 State對象。

那麼State中可以訪問創建它的StatefulWidget嗎?答案是肯定的。

在State中定義了一個T類型的widget, 這個T是StatefulWidget的子類:

abstract class State<T extends StatefulWidget> with Diagnosticable {
  T get widget => _widget!;
  T? _widget;

這裡的_widget是不需要我們自行去設置的,這個_widget是由flutter框架在調用initState之前設置的。該_widget實際上就是和State關聯的StatefulWidget。

我們可以直接在在State中使用widget來引用它。

State的生命周期

講完StatefulWidget和State的關係之後,接下來我們來了解一下State是如何變化的,通俗的講,就是State的生命周期是怎麼樣的。

通常來說,一個State的生命周期有4個狀態,分別是created,initialized,ready和defunct狀態,這四個狀態是定義在枚舉類_StateLifecycle中的:

enum _StateLifecycle {
  /// State已經被創建成功了,State.initState方法被調用。
  created,

  /// State.initState方法雖然被調用了,但是State對象還沒有構建完畢。 這時候會調用State.didChangeDependencies方法.
  initialized,

  /// State對象創建成功,State.dispose方法還沒有被調用。
  /// called.
  ready,

  /// State.dispose方法被調用過了,State對象不允許再調用build方法。
  defunct,
}

我們詳細來講解一下State的生命周期。

首先,flutter為了創建State對象,會調用StatefulWidget.createState方法。因為StatefulWidget.createState方法只是簡單的new一個State對象,所以這個時候State對象就處於created的狀態。

這個新創建的State對象會和一個BuildContext相關聯.注意這個關聯關係是永久性的,不會發生變化的。

雖然關聯關係不會發生變化,但是BuildContext本身是可以在樹上進行移動的。這時候的State處於mounted狀態。

接下來,flutter會調用State中的 initState方法。

對於State的具體實現來說,需要重寫這個initState的方法,根據和State關聯的BuildContext和Widget來初始化State的狀態。其中BuildContext和Widget可以通過使用State的context和widget屬性來訪問獲取。

然後flutter框架會調用state的didChangeDependencies方法。

什麼時候會去調用這個方法呢?根據flutter的說法,當State依賴的對象發生變化的時候就會調用。

舉個例子,如果在State的build方法中引用了一個InheritedWidget對象,而這個InheritedWidget對象後來發生了變化。這個時候flutter就會調用didChangeDependencies方法。

我們看下State中該方法的定義:

  void didChangeDependencies() { }

可以看到這個方法本身是一個空的方法體,因為並不是抽象方法,所以子類並不需要強制實現它。

為什麼一般來說State的子對象並不需要重寫這個方法呢?這是因為flutter如果檢測到依賴有變化的時候,會去調用State的build方法。通常來說,我們並不需要這麼頻繁的進行重構。

當然,也會有一些特殊的情況,比如實時網絡通訊這種實時性要求很高的情況。

這個時候,State對象完全初始化完畢了,接着就可以無限次數調用build方法,來重構用戶界面。

State還可以主動調用setState方法來重構子樹。

除了State主動調用setState方法之外,還有一些外部的變動會導致State的變動,比如:

void didUpdateWidget(covariant T oldWidget) { }

這個方法什麼時候會被調用呢?

我們知道Widget是不會變的,每個Widget都有一個唯一的key用來標記,但是parent Widget可以使用同一個key和runtimeType來對當前的widget進行修改。因為Widget是不變的,所以生成一個新的widget。這時候flutter就會調用State中的didUpdateWidget方法,並且將老的Widget作為參數傳入。

注意,flutter框架會在調用didUpdateWidget之後自動調用build方法,所以我們在寫程序的過程中,注意不要重複調用。

如果是在開發過程中,flutter還支持熱重載,這時候會調用state的reassemble方法:

void reassemble() { }

flutter框架會在觸發熱重載之後,調用build方法,所以一般來說,我們並不需要重寫reassemble方法。

剛剛我們提到了parent Widget可能修改當前Widget的配置文件,如果修改了當前Widget的key,那麼老的widget就處於一個deactivate 的狀態,widget中的deactivate 方法就會被調用:

  void deactivate() {
    super.deactivate();
    assert(
      !renderObject.attached,
      'A RenderObject was still attached when attempting to deactivate its '
      'RenderObjectElement: $renderObject',
    );
  }

我們可以重寫這個方法,來處理一些資源的清理工作。

注意,現在這個widget是deactivate狀態,但是並不意味這它就沒有用了。因為flutter還可以將這個widget再重新插入對象樹中,繼續使用。reinsert是通過調用State對象的build方法來實現的。

這個操作只要是在一個animation frame結束之前操作都是可以的。這樣做的好處就是,state還可以保留部分資源並不釋放,從而提升效率。

最後,如果State確實是不需要使用了,就會調用State的dispose 方法:

  void dispose() {
    assert(_debugLifecycleState == _StateLifecycle.ready);
    assert(() {
      _debugLifecycleState = _StateLifecycle.defunct;
      return true;
    }());
  }

當State的dispose方法被調用之後,State就處於unmounted狀態。這時候State的setState方法就不能再被調用了,這就表示State的生命周期結束了。

總結

以上就是State和State的生命周期相關的介紹。

更多內容請參考 //www.flydean.com/03-flutter-state/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!