flutter隨筆-瞭解widget

JonnyGG發表於2019-02-18

widget 簡介

Stateless vs Stateful

Stateless Widget

繼承自Widget, 重寫createElement() 方法:

@override
StatelessElement createElement() => new StatelessElement(this);
複製程式碼

StatelessElement 間接繼承自Element類,與StatelessWidget相對應(作為其配置資料).

StatelessWidget用於不需要維護狀態的場景,它通常在build方法中通過巢狀其它Widget來構建UI,在構建過程中會遞迴的構建其巢狀的Widget。

Stateful Widget

和StatelessWidget一樣,StatefulWidget也是繼承自widget類,並重寫了createElement()方法,不同的是返回的Element 物件並不相同;另外StatefulWidget類中新增了一個新的介面createState()

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

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

  @protected
  State createState();
}
複製程式碼
  • StatefulElement 間接繼承自Element類,與StatefulWidget相對應(作為其配置資料)。StatefulElement中可能會多次呼叫createState()來建立狀態(State)物件。
  • createState() 用於建立和Stateful widget相關的狀態,它在Stateful widget的生命週期中可能會被多次呼叫。例如,當一個Stateful widget同時插入到widget樹的多個位置時,Flutter framework就會呼叫該方法為每一個位置生成一個獨立的State例項,其實,本質上就是一個StatefulElement對應一個State例項。

State生命週期

CounterWidget接收一個initValue整形引數,它表示計數器的初始值。下面我們看一下State的程式碼:

class _CounterWidgetState extends State<CounterWidget> {  
  int _counter;

  @override
  void initState() {
    super.initState();
    //初始化狀態  
    _counter=widget.initValue;
    print("initState");
  }

  @override
  Widget build(BuildContext context) {
    print("build");
    return Center(
      child: FlatButton(
        child: Text('$_counter'),
        //點選後計數器自增  
        onPressed:()=>setState(()=> ++_counter) ,
      ),
    );
  }

  @override
  void didUpdateWidget(CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }

  @override
  void deactivate() {
    super.deactivate();
    print("deactive");
  }

  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }

}
複製程式碼

接下來,我們建立一個新路由,在新路由中,我們只顯示一個CounterWidget

Widget build(BuildContext context) {
  return CounterWidget();
}
複製程式碼

我們執行應用並開啟該路由頁面,在新路由頁開啟後,螢幕中央就會出現一個數字0,然後控制檯日誌輸出:

I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build
複製程式碼

可以看到,在StatefulWidget插入到Widget樹時首先initState方法會被呼叫。

然後我們點選⚡️按鈕熱過載,控制檯輸出日誌如下:

I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build
複製程式碼

可以看到此時initStatedidChangeDependencies都沒有被呼叫,而此時didUpdateWidget被呼叫。

接下來,我們在widget樹中移除CounterWidget,將路由build方法改為:

Widget build(BuildContext context) {
  //移除計數器 
  //return CounterWidget();
  //隨便返回一個Text()
  return Text("xxx");
}
複製程式碼

然後熱過載,日誌如下:

I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose
複製程式碼

我們可以看到,在CounterWidget從widget樹中移除時,deactivedispose會依次被呼叫。

下面我們來看看各個回撥函式:

  • initState():當Widget第一次插入到Widget樹時會被呼叫,對於每一個State物件,Flutter framework只會呼叫一次該回撥,所以,通常在該回撥中做一些一次性的操作,如狀態初始化、訂閱子樹的事件通知等。不能在該回撥中呼叫BuildContext.inheritFromWidgetOfExactType(該方法用於在Widget樹上獲取離當前widget最近的一個父級InheritFromWidget,關於InheritedWidget我們將在後面章節介紹),原因是在初始化完成後,Widget樹中的InheritFromWidget也可能會發生變化,所以正確的做法應該在在build()方法或didChangeDependencies()中呼叫它。
  • didChangeDependencies():當State物件的依賴發生變化時會被呼叫;例如:在之前build() 中包含了一個InheritedWidget,然後在之後的build()InheritedWidget發生了變化,那麼此時InheritedWidget的子widget的didChangeDependencies()回撥都會被呼叫。典型的場景是當系統語言Locale或應用主題改變時,Flutter framework會通知widget呼叫此回撥。
  • build():此回撥讀者現在應該已經相當熟悉了,它主要是用於構建Widget子樹的,會在如下場景被呼叫:
    1. 在呼叫initState()之後。
    2. 在呼叫didUpdateWidget()之後。
    3. 在呼叫setState()之後。
    4. 在呼叫didChangeDependencies()之後。
    5. 在State物件從樹中一個位置移除後(會呼叫deactivate)又重新插入到樹的其它位置之後。
  • reassemble():此回撥是專門為了開發除錯而提供的,在熱過載(hot reload)時會被呼叫,此回撥在Release模式下永遠不會被呼叫。
  • didUpdateWidget():在widget重新構建時,Flutter framework會呼叫Widget.canUpdate來檢測Widget樹中同一位置的新舊節點,然後決定是否需要更新,如果Widget.canUpdate返回true則會呼叫此回撥。正如之前所述,Widget.canUpdate會在新舊widget的key和runtimeType同時相等時會返回true,也就是說在在新舊widget的key和runtimeType同時相等時didUpdateWidget()就會被呼叫。
  • deactivate():當State物件從樹中被移除時,會呼叫此回撥。在一些場景下,Flutter framework會將State物件重新插到樹中,如包含此State物件的子樹在樹的一個位置移動到另一個位置時(可以通過GlobalKey來實現)。如果移除後沒有重新插入到樹中則緊接著會呼叫dispose()方法。
  • dispose():當State物件從樹中被永久移除時呼叫;通常在此回撥中釋放資源。

狀態管理

  • 如果狀態是使用者資料,如核取方塊的選中狀態、滑塊的位置,則該狀態最好由父widget管理。

  • 如果狀態是有關介面外觀效果的,例如顏色、動畫,那麼狀態最好由widget本身來管理。

  • 如果某一個狀態是不同widget共享的則最好由它們共同的父widget管理。

    有些時候,如果不確定到底該怎麼管理狀態,那麼推薦的首選是在父widget中管理(靈活會顯得更重要一些)

總結

Flutter提供了豐富的widget,在實際的開發中你可以隨意使用它們,不要怕引入過多widget庫會讓你的應用安裝包變大,這不是web開發,dart在編譯時只會編譯你使用了的程式碼。由於Material和Cupertino都是在基礎widget庫之上的,所以如果你的應用中引入了這兩者之一,則不需要再引入flutter/widgets.dart了,因為它們內部已經引入過了。

相關文章