Flutter 生命週期及渲染原理

語言: CN / TW / HK

這是我參與11月更文挑戰的第12天,活動詳情檢視:2021最後一次更文挑戰

Widget 生命週期

生命週期的基本概念

什麼是生命週期

我們使用一個物件的時候,有時會需要知道物件的一個狀態,什麼時候被建立,什麼時候被銷燬。我們常用的生命週期方法其實本質上就是回撥函式,是 Flutter 封裝好的,在 Widget 的不同狀態設定對應的回撥方法給外部使用。

生命週期的作用

  1. 初始化資料
    • 建立變數、常量
    • 傳送網路請求
  2. 監聽小部件的事件
  3. 管理記憶體
    • 銷燬資料、銷燬監聽者、定時器等

Widget 常見的生命週期函式

Widget 生命週期函式我們可以分為兩種型別來看,無狀態與有狀態型別。

無狀態 Widget (StatelessWidget)

當無狀態 Widget 比渲染的時候會依次呼叫建構函式與 build 函式。

有狀態 Widget (StatefulWidget)

``` class MyHomePage extends StatefulWidget { final String? title; MyHomePage({this.title}) { print('Widget 建構函式被呼叫了'); } @override _MyHomePageState createState() { print('createState 函式被呼叫了'); return _MyHomePageState(); } }

class _MyHomePageState extends State { _MyHomePageState() { print('State 建構函式被呼叫了'); } @override void initState() { super.initState(); print('State 的 init 函式被呼叫了'); } @override Widget build(BuildContext context) { print('State build 函式被呼叫了'); return Center(child: Text(widget.title ?? ''),); }

@override void dispose() { super.dispose(); print('State 的 dispose 函式被呼叫了'); } } ```

有狀態的 Widget 被渲染的時候會依次呼叫 StatefulWidget 的建構函式、createState 函式、State 的構造函、initState 函式、build 函式。當熱過載的時候會呼叫 StatefulWidget 的建構函式跟 Statebuild 函式。當呼叫 setState 方法的時候會呼叫 build 函式。

通過原始碼可以看到 setState 其實就是 _element 呼叫了 markNeedsBuild 函式,只是在此之前做了一些錯誤的判斷,這裡 _element 就是 context

資料共享部件 InheritedWidget

``` class MyData extends InheritedWidget { final int data; //需要在子元件中共享的資料

//構造方法 const MyData({required this.data, required Widget child}) : super(child: child);

//定義一個便捷方法,方便子元件中的 widget 獲取共享資料 static MyData? ofContext(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); }

//該回調決定當前 data 發生變化的時候,是否通知子元件(依賴 data 的子元件) @override bool updateShouldNotify(covariant InheritedWidget oldWidget) { print('呼叫了 updateShouldNotify 函式'); //如果返回 true,子部件中依賴共享資料的 Widget(build 函式中是否使用共享資料) 的 didChangeDependencies 方法會被呼叫 return (oldWidget as MyData).data != data; } }

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

@override _InheritedDemoState createState() => _InheritedDemoState(); }

class _InheritedDemoState extends State { int _count = 0;

@override Widget build(BuildContext context) { return MyData(data: _count, child: Column( children: [ TextDemo(count: _count), ElevatedButton(onPressed: () { _count++; setState(() {}); }, child: const Icon(Icons.add)), ], )); } }

class TextDemo extends StatefulWidget { final int? count; TextDemo({this.count}); @override _TextDemoState createState() => _TextDemoState(); }

class _TextDemoState extends State { @override Widget build(BuildContext context) { print('呼叫了 build 函式'); return Text((MyData.ofContext(context) as MyData).data.toString()); } @override void didChangeDependencies() { super.didChangeDependencies(); print('呼叫了 didChangeDependencies 函式'); } } ```

在我們開發的過程中一定會遇到這種場景,子部件的資料需要依賴父部件的資料,當層級比較多的話層層傳遞的方式就會比較麻煩,所以 Flutter 提供一個 InheritedWidget 部件,用來解決這種場景。如上案例中,我們定義了一個負責資料共享的類 MyData 繼承於 InheritedWidgetMyData 的構造方法中有兩個引數,data 代表需要共享的資料,child 表示依賴於資料共享的 Widget。並且我們提供了一個 ofContext 方法,供外界獲取資料時候使用。使用的話,我們在 _InheritedDemoStatebuild 方法中初始化 MyData,在需要獲取共享資料的子部件中通過 MyData.ofContext(context) 來獲取資料,這裡需要注意的是,子元件需要通過 InheritedWidget 來獲取共享資料的話,其根元件必須是繼承於 InheritedWidget 類的 MyData。當我們執行 _count++ 的時候會呼叫 updateShouldNotify 方法,在這裡我們可以通過返回值來判斷是否呼叫子元件的 didChangeDependencies 方法,類似於傳送通知,返回值為 true 的時候就會呼叫,反之就不呼叫,我們可以根據需求在 didChangeDependencies 方法中做一些事情。

Flutter 渲染原理

abstract class Widget extends DiagnosticableTree { Element createElement(); }

以上只附上了關鍵程式碼,通過原始碼我們可以看到 Widget 類都會實現 createElement 函式。這裡我們對於 Widget 的子類 StatelessWidgetStatefulWidget 的渲染流程分開來看。

StatelessWidget 渲染流程

abstract class StatelessWidget extends Widget { StatelessElement createElement() => StatelessElement(this); }

StatelessWidgetcreateElement 方法會建立一個 StatelessElement 物件並加入到 Elment 樹中,並且返回 StatelessElement 物件,建立 StatelessElement 物件的時候 StatelessWidget 自己作為引數傳給 StatelessElement 物件。

class StatelessElement extends ComponentElement { Widget build() => widget.build(this); }

abstract class ComponentElement extends Element { void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); assert(_child == null); assert(_lifecycleState == _ElementLifecycle.active); _firstBuild(); assert(_child != null); }

abstract class Element extends DiagnosticableTree implements BuildContext { Element(Widget widget) : assert(widget != null), _widget = widget;

通過原始碼追蹤可以看到 StatelessElement 繼承於 ComponentElementComponentElement 繼承於 Element,在 Element 的構造方法中外部傳入的 widget 會被賦值給 _widget 屬性,在 ComponentElement 中會呼叫 mount 方法, mount 方法中的 _firstBuild 最終會執行 ComponentElementbuild 方法,並且 StatelessElementbuild 會執行 widget.build(this),並把自己傳給外面。

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) {} }

所以我們外部 StatelessWidget 子類中的 context 就是 Element 物件。

StatefulWidget 渲染流程

abstract class StatefulWidget extends Widget { @override StatefulElement createElement() => StatefulElement(this); }

StatefulWidget 同樣會執行 createElement,但是返回的物件是 StatefulElement 型別。

``` class StatefulElement extends ComponentElement { StatefulElement(StatefulWidget widget) : _state = widget.createState(), super(widget) { state._element = this; state._widget = widget; }

@override Widget build() => state.build(this); } ```

但是 StatefulElement 多了一步就是執行 createState 函式,並且賦值給 _state 屬性,並且把外部傳入的 widget 賦值給 state._widget 屬性,把 this 指標賦值給 state._element。這也是我們能在 state 中能獲取到 widget 的原因。這裡 build 方法中執行的是 state.build(this)