從原始碼看flutter(一):Widget篇

安卓小哥發表於2020-04-14

開篇

我們知道,flutter中我們做的一切幾乎都是在和 Widget 打交道,那麼它在flutter中扮演著怎樣的角色,起到了什麼樣的作用呢?

我們將通過閱讀原始碼的方式,去解答關於它的各種疑問。

而這也是flutter知識拼圖中,我們選擇的第一塊。

Widget

可以從我們常用的 StatelessWidgetStatefulWidget 看到,他們都擁有同一個父類 Widget,我們將 Widget 作為起點,先看看一看它的構造

@immutable
abstract class Widget extends DiagnosticableTree {

  const Widget({ this.key });
  
  final Key key;
  
  @protected
  Element createElement();
  ...
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}
複製程式碼

Widget 是繼承於 DiagnosticableTree 的,關於 DiagnosticableTree 這個類,它主要用於在除錯時獲取子類的各種屬性和children資訊,在flutter各個物件中你經常能看到它,目前我們不需要去關心與之相關的內容

我們可以看到,Widget 是一個抽象類;同時它被 immutable 註解修飾,說明它的各個屬性一定是不可變的,這就是為什麼我們寫各種 Widget 時,所寫的各個屬性要加 final 的原因,否則編譯器就會發出警告

image

同時,我們看到了熟悉的 createElement() 方法,它交由子類去實現

還有 canUpdate(...) 方法, 如果返回 true 表示可以更新當前 Element 物件,並用新的 widget 更新 Elementwidget ,在下一篇關於 Element 的介紹中,我們能看到它的具體使用

接下來,我們看一看幾個飛車重要的 Widget 物件

StatelessWidget

abstract class StatelessWidget extends Widget {
  
  const StatelessWidget({ Key key }) : super(key: key);
  
  @override
  StatelessElement createElement() => StatelessElement(this);
  
  @protected
  Widget build(BuildContext context);
}

複製程式碼

StatelessWidget 也是一個抽象方法,提供了一個 build(context) 方法供子類實現,而這裡重寫的 createElement() 預設返回的是 StatelessElement(...) 物件,再來看看 StatefulWidget

StatefulWidget

abstract class StatefulWidget extends Widget {

  const StatefulWidget({ Key key }) : super(key: key);

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

  @protected
  State createState();
}
複製程式碼

StatefulWidget 同樣是一個非常簡單的抽象物件,這裡預設提供的是建立 StatefulElement 的方法。同時,提供了我們非常熟悉的 createState() 方法,它返回一個 State 物件,我們經常使用的 setState(...) 就在其中,來看一下它的實現

State

abstract class State<T extends StatefulWidget> extends Diagnosticable {
  T get widget => _widget;
  
  _StateLifecycle _debugLifecycleState = _StateLifecycle.created;
  
  BuildContext get context => _element;
  
  bool get mounted => _element != null;
  
  void initState() {
    assert(_debugLifecycleState == _StateLifecycle.created);
  }
  
  void didUpdateWidget(covariant T oldWidget) { }
  
  void reassemble() { }
  
  void setState(VoidCallback fn) {
    ...
    _element.markNeedsBuild();
  }

  void deactivate() { }
  
  void dispose() {
    ...
     _debugLifecycleState = _StateLifecycle.defunct;
    ...
  }
  
  Widget build(BuildContext context);
  
  void didChangeDependencies() { }
}

enum _StateLifecycle {

  created,

  initialized,

  ready,

  defunct,
}
複製程式碼

為了方便閱讀,上面省去了部分程式碼和所有註解,首先可以看到,StateDiagnosticable 的子類,而 Diagnosticable 物件就是之前 DiagnosticableTree 中內提供的結點,我們不需要關注它。

State 中持有了 BuildContext 物件和 Widget 物件, BuildContextElement 實現的介面,下一篇會講到它。同時,我們可以發現 State 是具備生命週期的,分別是四種情況:

  • created: 預設的生命週期
  • initialized: initState() 被呼叫後的生命週期
  • ready: didChangeDependencies() 被呼叫後的生命週期, 當生命週期為這個時就準備呼叫 build() 方法了
  • defunct: dispose() 被呼叫後的生命週期

其中第二個第三個都是在 StatefulElement 中被改變的,可以簡單的看一看

class StatefulElement extends ComponentElement {
    ...
  @override
  void _firstBuild() {
    ...
      final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
    ...
      _state._debugLifecycleState = _StateLifecycle.initialized;
    ...
    _state.didChangeDependencies();
    ...
      _state._debugLifecycleState = _StateLifecycle.ready;
    ...
  }
  ...
  @override
  void unmount() {
    super.unmount();
    _state.dispose();
    ...
  }
}
複製程式碼

至於哪裡呼叫了 _firstBuild() 我們放到第二篇講

還有一些方法,我們簡單介紹一下:

  • didUpdateWidget(...): 當 Widget 更新時,會呼叫它。可以重寫這個方法做一些資料修改的操作,比如更新資料,但是不要在這個方法中呼叫 setState 因為這樣就重複重新整理了
  • reassemble(): 這個方法是debug時熱過載會觸發的,我們一般不需要去管它
  • deactivate(): 當前 Widget 從樹中移除的時候,會呼叫這個方法,它會比 dispose() 先呼叫

還有 setState(...) 是我們最常用的一個方法,這個方法中並沒有觸發傳入的 VoidCallback ,而是呼叫了 ElementmarkNeedsBuild() ,這也是為什麼我們把修改資料的邏輯放到 setState(...) 前、中、後,資料都能重新整理的原因,而真正導致資料重新整理的邏輯,等第二篇就知道啦。

關於 State 的部分就差不多了。不知道這時候你是否會產生一個疑問,因為我們常常聽說 WidgetElmenetRenderObject 這三個物件的關係是十分緊密的。不過到上面只看到了 WidgetElement ,完全沒有 RenderObject 的身影,那麼它是在 Elmenet 中建立的嗎?

這裡我先提前說明一下,並不是。因為 Elment 中並沒有相關的 RenderObject 建立方法,到這裡問題來了,RenderObject 的建立方法,在哪裡呢?

下面將介紹另一個非常重要,但是我們很少使用的 Widget 物件

RenderObjectWidget

abstract class RenderObjectWidget extends Widget {
    
  const RenderObjectWidget({ Key key }) : super(key: key);
  
  @override
  RenderObjectElement createElement();
  
  @protected
  RenderObject createRenderObject(BuildContext context);
  
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  
  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
複製程式碼

可以看到,RenderObject 的建立方法其實是由 RenderObjectWidget 提供的,同時還提供了對應的 RenderObjectElement 建立方法。關於這個 Widget 以及 RenderObject 相關資訊,留到第三篇。本篇就結束了。

總結

通過上面一番原始碼閱讀,我們可以發現 Widget 具備下面的一些特性:

  • Widget 中所持有的物件都是不可修改的,也就是說它不具備資料的儲存功能,而只能夠對資料進行傳遞;即便是 StatefulWidget,資料的儲存也是放在 State 中,它只是提供了 State 的建立方法
  • 每個 Widget 都提供了 Element 的建立方法,但只有部分 Widget 具備 RenderObject 的建立方法。從這裡我們可以斷定, ElementRenderObject 不是一一對應的關係,很可能是一對一或者多對一的關係;同時我們猜想, ElementWidget 是一一對應的關係。

僅僅根據上面這些特性,並不能給 Widget 下一個準確的定義,那是因為我們還沒深入瞭解主要的 RenderObject 。不過這裡為了結束這篇文章,還是先提前做個定義吧:

  • RenderObject 用於佈局繪製等操作,那麼 RenderObjectWidget 則是向 RenderObject 提供繪製所需要的配置引數
  • State 用於進行資料的儲存與修改, 那麼 StatefulWidget 則是用於向 State 傳遞配置引數
  • StatelessWidget 用於向其他 Widget 傳遞配置引數。

Widget 之間又可以相互組合,最後得出結論:Widget 用於描述描當前的配置和狀態下檢視所應該呈現的樣子

相關文章