[譯] Flutter 核心概念詳解: Widget、State、Context 及 InheritedWidget

淚已無痕發表於2019-02-27

本文涵蓋了 Flutter 應用中有關 Widget、State、Context 及 InheritedWidget 的重要概念。因為 InheritedWidget 是最重要且文件缺乏的部件之一,故需特別關注。

難度:初學者

前言

Flutter 中的 WidgetStateContext 是每個 Flutter 開發者都需要充分理解的最重要的概念之一。

雖然存在大量文件,但並沒有一個能夠清晰地解釋它。

我將用自己的語言來解釋這些概念,知道這些可能會讓一些純理論者感到不安,但本文的真正目的是試圖說清以下主題:

  • Stateful Widget 和 Stateless Widget 的區別
  • Context 是什麼
  • State 是什麼並且如何使用它
  • context 與其 state 物件之間的關係
  • InheritedWidget 及在 Widgets 樹中傳播資訊的方式
  • 重建的概念

本文同時釋出於 Medium - Flutter Community

第一部分:概念

Widget 的概念

Flutter 中,幾乎所有的東西都是 Widget

將一個 Widget 想象為一個視覺化元件(或與應用視覺化方面互動的元件)。

當你需要構建與佈局直接或間接相關的任何內容時,你正在使用 Widget

Widget 樹的概念

Widget 以樹結構進行組織。

包含其他 Widget 的 Widget 被稱為父 Widget(或Widget 容器)。包含在父 Widget 中的 Widget 被稱為子 Widget

讓我們用 Flutter 自動生成的基礎應用來說明這一點。以下是簡化程式碼,僅有 build 方法:

@override
Widget build(BuildContext){
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
}
複製程式碼

如果我們現在觀察這個基本示例,我們將獲得以下 Widget 樹結構(限制程式碼中存在的 Widget 列表):

state diagram basic

Context 的概念

另外一個重要的概念是 Context

Context 僅僅是已建立的所有 Widget 樹結構中某個 Widget 的位置引用。

簡而言之,將 context 作為 Widget 樹的一部分,其中 context 所對應的 Widget 被新增到此樹中。

一個 context 僅僅從屬於一個 widget。

如果 widget ‘A’ 擁有子 widget,那麼 widget ‘A’ 的 context 將成為其直接關聯子 context父 context

讀到這裡會很明顯發現 context 是連結在一起的,並且會形成一個 context 樹(父子關係)。

如果我們現在嘗試在上圖中說明 Context 的概念,我們得到(依舊是一個非常簡化的檢視)每種顏色代表一個 context除了 MyApp,它是不同的):

state diagram basic context

Context 可見性 (簡短描述):

某些東西 只能在自己的 context 或在其父 context 中可見。

通過上述描述我們可以將其從子 context 中提取出來,它很容易找到一個 祖先(= 父)Widget。

一個例子,考慮 Scaffold > Center > Column > Text:context.ancestorWidgetOfExactType(Scaffold) => 通過從 Text 的 context 得到樹結構來返回第一個 Scaffold。

從父 context 中,也可以找到 後代(= 子)Widget,但不建議這樣做(我們將稍後討論)。

Widget 的型別

Widget 擁有 2 種型別:

Stateless Widget

這些視覺化元件除了它們自身的配置資訊外不依賴於任何其他資訊,該資訊在其直接父節點構建時提供。

換句話說,這些 Widget 一旦建立就不關心任何變化

這樣的 Widget 稱為 Stateless Widget

這種 Widget 的典型示例可以是 Text、Row、Column 和 Container 等。在構建時,我們只需將一些引數傳遞給它們。

引數可以是裝飾、尺寸、甚至其他 widget 中的任何內容。需要強調的是,該配置一旦被建立,在下次構建過程之前都不會改變。

stateless widget 只有在 loaded/built 時才會繪製一次,這意味著任何事件或使用者操作都無法對該 Widget 進行重繪。

Stateless Widget 生命週期

以下是與 Stateless Widget 相關的典型程式碼結構。

如下所示,我們可以將一些額外的引數傳遞給它的建構函式。但請記住,這些引數在後續階段將改變(變化),並且必須按照已有狀態使用。

class MyStatelessWidget extends StatelessWidget {

	MyStatelessWidget({
		Key key,
		this.parameter,
	}): super(key:key);

	final parameter;

	@override
	Widget build(BuildContext context){
		return new ...
	}
}
複製程式碼

即使有另一個方法可以被重寫(createElement),後者也幾乎不會被重寫。唯一需要被重寫的是 build 方法。

這種 Stateless Widget 的生命週期是相當簡單的:

  • 初始化
  • 通過 build() 渲染

Stateful Widget

其他一些 Widget 將處理一些在 Widget 生命週期內會發生變化的內部資料。因此,此類資料會變為動態

該 Widget 所持有的資料集在其生命週期內可能會發生變化,這樣的資料被稱為 State

這些 Widget 被稱為 Stateful Widget

此類 Widget 的示例可能是使用者可選擇的核取方塊列表,也可以是根據條件禁用的 Button 按鈕。

State 的概念

State 定義了 StatefulWidget 例項的 “行為”。

它包含了用於 互動 / 干預 Widget 資訊:

  • 行為
  • 佈局

應用於 State 的任何更改都會強制 Widget 進行重建

State 和 Context 的關係

對於 Stateful WidgetStateContext 相關聯。並且此關聯是永久性的,State 物件將永遠不會改變其 context

即使可以在樹結構周圍移動 Widget Context,State 仍將與該 context 相關聯。

StateContext 關聯時,State 被視為已掛載

重點

State 物件context 相關聯,就意味著該 State 物件(直接)訪問另一個 context!(我們將在稍後討論該問題)。


Stateful Widget 生命週期

既然已經介紹了基本概念,現在是時候更加深入一點了……

以下是與 Stateful Widget 相關的典型程式碼結構。

由於本文的主要目的是用“變數”資料來解釋 State 的概念,我將故意跳過任何與 Stateful Widget 相關的一些可重寫方法的解釋,這些方法與此沒有特別的關係。這些可重寫的方法是 didUpdateWidget、deactivate 和 reassemble。這些內容將在下一篇文章中討論。

class MyStatefulWidget extends StatefulWidget {

	MyStatefulWidget({
		Key key,
		this.parameter,
	}): super(key: key);

	final parameter;

	@override
	_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {

	@override
	void initState(){
		super.initState();

		// Additional initialization of the State
	}

	@override
	void didChangeDependencies(){
		super.didChangeDependencies();

		// Additional code
	}

	@override
	void dispose(){
		// Additional disposal code

		super.dispose();
	}

	@override
	Widget build(BuildContext context){
		return new ...
	}
}
複製程式碼

下圖展示了與建立 Stateful Widget 相關的操作/呼叫序列(簡化版本)。在圖的右側,你將注意到資料流中 State 物件的內部狀態。你還將看到此時 context 與 state 已經關聯,並且 context 因此變為可用狀態(mounted)。

state diagram

接下來讓我們通過一些額外的細節來解釋它:

initState()

一旦 State 物件被建立,initState() 方法是第一個(建構函式之後)被呼叫的方法。當你需要執行額外的初始化時,該方法將會被重寫。常見的初始化與動畫、控制器等相關。如果重寫該方法,你應該首先呼叫 super.initState()

該方法可以得到 context,但無法真正使用它,因為框架還沒有完全將其與 state 關聯。

一旦 initState() 方法執行完成,State 物件就被初始化並且 context 變為可用。

在該 State 物件的生命週期內將不會再次呼叫此方法。

didChangeDependencies()

didChangeDependencies() 方法是第二個被呼叫的方法。

在這一階段,由於 context 是可用的,所以你可以使用它。

如果你的 Widget 連結到了一個 InheritedWidget 並且/或者你需要初始化一些 listeners(基於 context),通常會重寫該方法。

請注意,如果你的 widget 連結到了一個 InheritedWidget,在每次重建該 Widget 時都會呼叫該方法。

如果你重寫該方法,你應該首先呼叫 super.didChangeDependencies()

build()

build(BuildContext context) 方法在 didChangeDependencies()(及 didUpdateWidget)之後被呼叫。

這是你構建你的 widget(可能還有任何子樹)的地方。

每次 State 物件更新(或當 InheritedWidget 需要通知“已註冊” widget)時都會呼叫該方法!!

為了強制重建,你可能需要呼叫 setState((){…}) 方法。

dispose()

dispose() 方法在 widget 被廢棄時被呼叫。

如果你需要執行一些清理操作(比如:listeners),則重寫該方法,並在此之後立即呼叫 super.dispose()

Stateless 或 Stateful Widget?

這是許多開發者都需要問自己的問題:我是否需要 Widget 為 Stateless 或 Stateful?

為了回答這個問題,請問問自己:

在我的 widget 生命週期中,是否需要考慮一個將要變更,並且在變更後 widget 將強制重建變數

如果問題的答案是 yes,那麼你需要一個 Stateful Widget,否則,你需要一個 Stateless Widget。

一些例子:

  • 用於顯示覆選框列表的 widget。要顯示覆選框,你需要考慮一系列專案。每個專案都是一個包含標題和狀態的物件。如果你點選一個核取方塊,相應的 item.status 將會切換;

    在這種情況下,你需要使用一個 Stateful Widget 來記住專案的狀態,以便能夠重繪核取方塊。

  • 帶有表格的螢幕。該螢幕允許使用者填寫表單的 Widget 並將表單傳送到伺服器。

    在這種情況下,除非你要對錶單進行驗證,或在提交之前做一些其他的事情,一個 Stateless Widget 可能就足夠了。


Stateful Widget 由 2 部分組成

還記得 Stateful widget 的結構嗎?有 2 個部分:

Widget 定義

class MyStatefulWidget extends StatefulWidget {
    MyStatefulWidget({
		Key key,
		this.color,
	}): super(key: key);

	final Color color;

	@override
	_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}
複製程式碼

第一部分 “MyStatefulWidget” 通常是 Widget 的 public 部分。當你需要將其新增到 widget 樹時,可以例項化它。該部分在 Widget 生命週期內不會發生變化,但可能接受與其相關的 State 例項化時使用的引數。

請注意,在 Widget 第一部分定義的任何變數通常在其生命週期內不會發生變化。

Widget State 定義

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
    ...
	@override
	Widget build(BuildContext context){
	    ...
	}
}
複製程式碼

第二部分 “_MyStatefulWidgetStat” 管理 Widget 生命週期中的變化,並強制每次應用修改時重建該 Widget 例項。名稱開頭的 ‘_’ 字元使得該類對 .dart 檔案是私有的

如果你需要在 .dart 檔案之外引用此類,請不要使用 ‘_’ 字首。

_MyStatefulWidgetState 類可以通過使用 widget.{變數名稱} 來訪問被儲存在 MyStatefulWidget 中的任何變數。在該示例中為:widget.color


Widget 唯一標識 - Key

在 Fultter 中,每一個 Widget 都是被唯一標識的。這個唯一標識在 build/rendering 階段由框架定義。

該唯一標識對應於可選的 Key 引數。如果省略該引數,Flutter 將會為你生成一個。

在某些情況下,你可能需要強制使用此 key,以便可以通過其 key 訪問 widget。

為此,你可以使用以下方法中的任何一個:GlobalKeyLocalKeyUniqueKeyObjectKey

GlobalKey 確保生成的 key 在整個應用中是唯一的。

強制 Widget 使用唯一標識:

GlobalKey myKey = new GlobalKey();
...
@override
Widget build(BuildContext context){
    return new MyWidget(
        key: myKey
    );
}
複製程式碼

第二部分:如何訪問 State?

如前所述,State 被連結到 一個 Context,並且一個 Context 被連結到一個 Widget 例項

1. Widget 自身

從理論上講,唯一能夠訪問 State 的是 Widget State 自身

在此中情況下不存在任何困難。Widget State 類可以訪問任何內部變數。

2. 直接子 Widget

有時,父 widget 可能需要訪問其直接子節點的 State 才能執行特定任務。

在這種情況下,要訪問這些直接子節點的 State,你需要瞭解它們。

呼叫某人的最簡單方法是通過名字。在 Flutter 中,每個 Widget 都有一個唯一的標識,由框架在 build/rendering 時確定。如前所示,你可以使用 key 引數為 Widget 強制指定一個標識。

...
GlobalKey<MyStatefulWidgetState> myWidgetStateKey = new GlobalKey<MyStatefulWidgetState>();
...
@override
Widget build(BuildContext context){
    return new MyStatefulWidget(
        key: myWidgetStateKey,
        color: Colors.blue,
    );
}
複製程式碼

一經確定, Widget 可以通過以下形式訪問其子節點的 State

myWidgetStateKey.currentState

讓我們考慮當使用者點選按鈕時顯示 SnackBar 這樣一個基本示例。由於 SnackBar 是 Scaffold 的子 Widget,它不能被 Scaffold 內部任何其他子節點直接訪問(還記得 context 的概念以及其層次/樹結構嗎?)。因此,訪問它的唯一方法是通過 ScaffoldState,它暴露出一個公共方法來顯示 SnackBar。

class _MyScreenState extends State<MyScreen> {
    /// the unique identity of the Scaffold
    final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

    @override
    Widget build(BuildContext context){
        return new Scaffold(
            key: _scaffoldKey,
            appBar: new AppBar(
                title: new Text('My Screen'),
            ),
            body: new Center(
                new RaiseButton(
                    child: new Text('Hit me'),
                    onPressed: (){
                        _scaffoldKey.currentState.showSnackBar(
                            new SnackBar(
                                content: new Text('This is the Snackbar...'),
                            )
                        );
                    }
                ),
            ),
        );
    }
}
複製程式碼

3. 祖先 Widget

假設你有一個屬於另一個 Widget 的子樹的 Widget,如下圖所示。

state child get state

為了實現這一目標,需要滿足 3 個條件:

1. 『帶有 State 的 Widget』(紅色)需要暴露其 State

為了暴露State,Widget 需要在建立時記錄它,如下所示:

class MyExposingWidget extends StatefulWidget {

   MyExposingWidgetState myState;

   @override
   MyExposingWidgetState createState(){
      myState = new MyExposingWidgetState();
      return myState;
   }
}
複製程式碼

2. “Widget State” 需要暴露一些 getter/setter 方法

為了讓“其他類” 設定/獲取 State 中的屬性,Widget State 需要通過以下方式授權訪問:

  • 公共屬性(不推薦)
  • getter / setter

例子:

class MyExposingWidgetState extends State<MyExposingWidget>{
   Color _color;

   Color get color => _color;
   ...
}
複製程式碼

3. “對獲取 State 感興趣的 Widget”(藍色)需要得到 State 的引用

class MyChildWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context){
      final MyExposingWidget widget = context.ancestorWidgetOfExactType(MyExposingWidget);
      final MyExposingWidgetState state = widget?.myState;

      return new Container(
         color: state == null ? Colors.blue : state.color,
      );
   }
}
複製程式碼

這個解決方案很容易實現,但子 widget 如何知道它何時需要重建呢?

通過此方案,它無能為力。它必須等到重建發生後才能重新整理其內容,此方法不是特別方便。

下一節將討論 Inherited Widget 的概念,它可以解決這個問題。


InheritedWidget

簡而言之,InheritedWidget 允許在 widget 樹中有效地向下傳播(和共享)資訊。

InheritedWidget 是一個特殊的 Widget,它將作為另一個子樹的父節點放置在 Widget 樹中。該子樹的所有 widget 都必須能夠與該 InheritedWidget 暴露的資料進行互動

基礎

為了解釋它,讓我們思考以下程式碼片段:

class MyInheritedWidget extends InheritedWidget {
   MyInheritedWidget({
      Key key,
      @required Widget child,
      this.data,
   }): super(key: key, child: child);

   final data;

   static MyInheritedWidget of(BuildContext context) {
      return context.inheritFromWidgetOfExactType(MyInheritedWidget);
   }

   @override
   bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}
複製程式碼

以上程式碼定義了一個名為 “MyInheritedWidget” 的 Widget,目的在於為子樹中的所有 widget 提供某些『共享』資料。

如前所述,為了能夠傳播/共享某些資料,需要將 InheritedWidget 放置在 widget 樹的頂部,這解釋了傳遞給 InheritedWidget 基礎建構函式的 @required Widget child 引數。

static MyInheritedWidget of(BuildContext context) 方法允許所有子 widget 通過包含的 context 獲得最近的 MyInheritedWidget 例項(參見後面的內容)。

最後重寫 updateShouldNotify 方法用來告訴 InheritedWidget 如果對資料進行了修改,是否必須將通知傳遞給所有子 widget(已註冊/已訂閱)(請參考下文)。

因此,我們需要將它放在樹節點級別,如下所示:

class MyParentWidget... {
   ...
   @override
   Widget build(BuildContext context){
      return new MyInheritedWidget(
         data: counter,
         child: new Row(
            children: <Widget>[
               ...
            ],
         ),
      );
   }
}
複製程式碼

子節點如何訪問 InheritedWidget 的資料?

在構建子節點時,後者將獲得 InheritedWidget 的引用,如下所示:

class MyChildWidget... {
   ...

   @override
   Widget build(BuildContext context){
      final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);

      ///
      /// 此刻,該 widget 可使用 MyInheritedWidget 暴露的資料
      /// 通過呼叫:inheritedWidget.data
      ///
      return new Container(
         color: inheritedWidget.data.color,
      );
   }
}
複製程式碼

如何在 Widget 之間進行互動?

請思考下圖中所顯示的 widget 樹結構。

inheritedwidget tree

為了說明互動方式,我們做以下假設:

  • ‘Widget A’ 是一個將專案新增到購物車裡的按鈕;
  • ‘Widget B’ 是一個顯示購物車中商品數量的文字;
  • ‘Widget C’ 位於 Widget B 旁邊,是一個內建任意文字的文字;
  • 我們希望 ‘Widget A’ 在按下時 ‘Widget B’ 能夠自動在購物車中顯示正確數量的專案,但我們不希望重建 ‘Widget C’

針對該場景,InheritedWidget 是唯一一個合適的 Widget 選項!

示例程式碼

我們先寫下程式碼然後再進行解釋:

class Item {
   String reference;

   Item(this.reference);
}

class _MyInherited extends InheritedWidget {
  _MyInherited({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final MyInheritedWidgetState data;

  @override
  bool updateShouldNotify(_MyInherited oldWidget) {
    return true;
  }
}

class MyInheritedWidget extends StatefulWidget {
  MyInheritedWidget({
    Key key,
    this.child,
  }): super(key: key);

  final Widget child;

  @override
  MyInheritedWidgetState createState() => new MyInheritedWidgetState();

  static MyInheritedWidgetState of(BuildContext context){
    return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }
}

class MyInheritedWidgetState extends State<MyInheritedWidget>{
  /// List of Items
  List<Item> _items = <Item>[];

  /// Getter (number of items)
  int get itemsCount => _items.length;

  /// Helper method to add an Item
  void addItem(String reference){
    setState((){
      _items.add(new Item(reference));
    });
  }

  @override
  Widget build(BuildContext context){
    return new _MyInherited(
      data: this,
      child: widget.child,
    );
  }
}

class MyTree extends StatefulWidget {
  @override
  _MyTreeState createState() => new _MyTreeState();
}

class _MyTreeState extends State<MyTree> {
  @override
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text('Title'),
        ),
        body: new Column(
          children: <Widget>[
            new WidgetA(),
            new Container(
              child: new Row(
                children: <Widget>[
                  new Icon(Icons.shopping_cart),
                  new WidgetB(),
                  new WidgetC(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Text('${state.itemsCount}');
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Text('I am Widget C');
  }
}
複製程式碼

說明

在這個非常基本的例子中:

  • _MyInherited 是一個 InheritedWidget,每次我們通過 ‘Widget A’ 按鈕新增一個專案時它都會重新建立
  • MyInheritedWidget 是一個 State 包含了專案列表的 Widget。可以通過 static MyInheritedWidgetState of(BuildContext context) 訪問該 State
  • MyInheritedWidgetState 暴露了一個獲取 itemsCount 的 getter 方法 和一個 addItem 方法,以便它們可以被 widget 使用,這是 widget 樹的一部分
  • 每次我們將專案新增到 State 時,MyInheritedWidgetState 都會重建
  • MyTree 類僅構建了一個 widget 樹,並將 MyInheritedWidget 作為樹的根節點
  • WidgetA 是一個簡單的 RaisedButton,當按下它時,它將從最近MyInheritedWidget 例項中呼叫 addItem 方法
  • WidgetB 是一個簡單的 Text,用來顯示最近 級別 MyInheritedWidget 的專案數

這一切是如何運作的呢?

為後續的通知註冊 Widget

當一個子 Widget 呼叫 MyInheritedWidget.of(context) 時,它傳遞自身的 context 並呼叫 MyInheritedWidget 的以下方法。

static MyInheritedWidgetState of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
複製程式碼

在內部,除了簡單地返回 MyInheritedWidgetState 例項外,它還訂閱消費者 widget 以便用於通知更改。

在幕後,對這個靜態方法的簡單呼叫實際上做了 2 件事:

  • 消費者 widget 被自動新增到訂閱者列表中,從而當對 InheritedWidget(這裡是 _MyInherited)應用修改時,該 widget 能夠重建
  • _MyInherited widget(又名 MyInheritedWidgetState)中引用的資料將返回給消費者

流程

由於 ‘Widget A’ 和 ‘Widget B’ 都使用 InheritedWidget 進行了訂閱,因此如果對 _MyInherited 應用了修改,那麼當點選 Widget A 的 RaisedButton 時,操作流程如下(簡化版本):

  1. 呼叫 MyInheritedWidgetStateaddItem 方法
  2. _MyInheritedWidgetState.addItem 方法將新專案新增到列表中
  3. 呼叫 setState() 以重建 MyInheritedWidget
  4. 通過列表中的新內容建立 _MyInherited 新的例項
  5. _MyInherited 記錄通過引數(data)傳遞的新 State
  6. 作為 InheritedWidget,它會檢查是否需要通知消費者(答案是 true)
  7. 遍歷整個消費者列表(這裡是 Widget A 和 Widget B)並請求它們重建
  8. 由於 Wiget C 不是消費者,因此不會重建。

至此它能夠有效工作!

然而,Widget A 和 Widget B 都被重建了,但由於 Wiget A 沒有任何改變,因此它沒有重建的必要。那麼應該如何防止此種情況發生呢?

在繼續訪問 Inherited Widget 的同時阻止某些 Widget 重建

Widget A 同時被重建的原因是由於它訪問 MyInheritedWidgetState 的方式。

如前所述,呼叫 context.inheritFromWidgetOfExactType() 方法實際上會自動將 Widget 訂閱到消費者列表中。

避免自動訂閱,同時仍然允許 Widget A 訪問 MyInheritedWidgetState 的解決方案是通過以下方式改造 MyInheritedWidget 的靜態方法:

static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
    return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
                    : context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
複製程式碼

通過新增一個 boolean 型別的額外引數……

  • 如果 rebuild 引數為 true(預設值),我們使用普通方法(並且將 Widget 新增到訂閱者列表中)
  • 如果 rebuild 引數為 false,我們仍然可以訪問資料,不使用 InheritedWidget內部實現

因此,要完成此方案,我們還需要稍微修改一下 Widget A 的程式碼,如下所示(我們新增值為 false 的額外引數):

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context, false);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item');
        },
      ),
    );
  }
}
複製程式碼

就是這樣,當我們按下 Widget A 時,它不會再重建了。

Routes、Dialogs 的一些特別說明……

Routes、Dialogs 的 context 與 Application 繫結。

這意味著即使在螢幕 A 內部你要求顯示另一個螢幕 B(例如,在當前的螢幕上),也無法輕鬆地從兩個螢幕中的任何一個關聯它們自己的 context。

螢幕 B 想要了解螢幕 A 的 context 的唯一方法是通過螢幕 A 得到它並將其作為引數傳遞給 Navigator.of(context).push(….)

有趣的連結

結論

關於這些主題還有很多話要說……,特別是在 InheritedWidget 上。

在下一篇文章中我將介紹 Notifiers / Listeners 的概念,它們使用 State 和資料傳遞的方式上同樣非常有趣。

所以,請保持關注和快樂編碼。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章