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

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

難度:初學者前端

前言

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

雖然存在大量文檔,但並無一個可以清晰地解釋它。android

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

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

本文同時發佈於 Medium - Flutter Communitygit

第一部分:概念

Widget 的概念

Flutter 中,幾乎全部的東西都是 Widgetgithub

將一個 Widget 想象爲一個可視化組件(或與應用可視化方面交互的組件)。後端

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

Widget 樹的概念

Widget 以樹結構進行組織。markdown

包含其餘 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索