初略講解Flutter的狀態管理

響應式的編程框架中都會有一個永恆的主題——「狀態管理」,不管是React/Vue(二者都是支持響應式編程的Web開發框架)仍是Flutter,討論的問題和解決的思想都是一致的。因此,若是你對React/Vue的狀態管理有了解,能夠跳過本節。言歸正傳,咱們想一個問題,Stateful Widget的狀態應該被誰管理?是Widget自己?是父Widget?仍是都會?亦或是另外一個對象?答案是:取決於實際狀況!編程

如下是管理狀態的最多見的方式:redux

  • Widget管理自身的狀態;
  • 父Widget管理子Widget的狀態;
  • 混合管理(父Widget和子Widget都管理狀態)。

如下原則能夠幫助你決定如何決定使用哪一種管理方式?bash

  • 若是狀態是用戶數據,如複選框的選中狀態、滑塊的位置,則該狀態最好由父Widget來管理;
  • 若是狀態是有關界面外觀效果的,如顏色、動畫,則該狀態最好由Widget自己來管理;
  • 若是某一個狀態是不一樣的Widget共享的,則最好由它們共同的父Widget來管理。

在Widget內部管理狀態封裝性會好一些,而在父Widget中管理會比較靈活。有些時候,若是不肯定到底該由誰來管理狀態,那麼首選由父Widget來管理(由於靈活會顯得更重要一些)。框架

接下來,咱們將經過建立三個簡單示例TapboxA、TapboxB和TapboxC來講明管理狀態的不一樣方式。這些例子的功能是類似的——建立一個盒子,當點擊它時,盒子背景會在綠色與灰色之間切換,狀態_active肯定顏色:綠色爲true,灰色爲falseless

下面的例子將使用GestureDetector來識別點擊事件,關於該GestureDetector的詳細內容咱們將在後續進行講解。ide

方式一:Widget管理自身狀態

_TapboxAState類:函數

  • 管理TapboxA的狀態;
  • 定義_active:肯定盒子的當前顏色的布爾值;
  • 定義_handleTap()函數,該函數在點擊該盒子時更新_active,並調用setState()更新UI;
  • 實現Widget的全部交互式行爲。
// TapboxA 管理自身狀態.

//------------------------- TapboxA ----------------------------------

class TapboxA extends StatefulWidget {
  TapboxA({Key key}) : super(key: key);

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

class _TapboxAState extends State<TapboxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: _handleTap,
      child: new Container(
        child: new Center(
          child: new Text(
            _active ? 'Active' : 'Inactive',
            style: new TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}
複製代碼

方式二:父Widget管理子Widget的狀態

對於父Widget來講,管理狀態並告訴其子Widget什麼時候更新一般是比較好的方式。例如,IconButton是一個圖片按鈕,但它是一個無狀態的Widget,由於咱們認爲父Widget須要知道該按鈕是否被點擊從而採起相應的處理。動畫

在如下示例中,TapboxB經過回調將其狀態導出到其父項。因爲TapboxB無論理任何狀態,所以它的父類爲StatefulWidget,TapboxB爲StatelessWidget。ui

ParentWidgetState類:this

  • 爲TapboxB管理_active狀態;
  • 實現_handleTapboxChanged(),當盒子被點擊時調用的方法;
  • 當狀態改變時,調用setState()更新UI。

TapboxB類:

  • 繼承StatelessWidget類,由於全部狀態都由其父Widget處理;
  • 當檢測到點擊時,它會通知父Widget。
// ParentWidget 爲 TapboxB 管理狀態.

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => new _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  TapboxB({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: _handleTap,
      child: new Container(
        child: new Center(
          child: new Text(
            active ? 'Active' : 'Inactive',
            style: new TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}
複製代碼

方式三:混合管理

對於一些Widget來講,混合管理的方式很是有用,在這種狀況下,Widget自身管理一些內部狀態,而父Widget管理一些其它外部狀態。

在下面TapboxC示例中,按下時,盒子的周圍會出現一個深綠色的邊框,擡起時,邊框消失,點擊生效,盒子的顏色改變。TapboxC將其_active狀態導出到其父Widget中,但在內部管理其_highlight狀態。這個例子有兩個狀態對象_ParentWidgetCState_TapboxCState

_ParentWidgetCState對象:

  • 管理_active狀態;
  • 實現_handleTapboxChanged(),當盒子被點擊時調用;
  • 當點擊盒子而且_active狀態改變時調用setState()方法更新UI。

_TapboxCState對象:

  • 管理_highlight狀態;
  • GestureDetector監聽全部Tap事件,當用戶按下時,添加高亮(深綠色邊框),當用戶擡起(釋放按下)時,會移除高亮;
  • 當按下、擡起或者取消點擊時更新_highlight狀態,調用setState()方法更新UI;
  • 當按下時,將狀態的改變傳遞給父Widget。
//---------------------------- ParentWidget ----------------------------

class ParentWidgetC extends StatefulWidget {
  @override
  _ParentWidgetCState createState() => new _ParentWidgetCState();
}

class _ParentWidgetCState extends State<ParentWidgetC> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapboxC(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//----------------------------- TapboxC ------------------------------

class TapboxC extends StatefulWidget {
  TapboxC({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;

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

class _TapboxCState extends State<TapboxC> {
  bool _highlight = false;

  void _handleTapDown(TapDownDetails details) {
    setState(() {
      _highlight = true;
    });
  }

  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTapCancel() {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTap() {
    widget.onChanged(!widget.active);
  }

  @override
  Widget build(BuildContext context) {
    // 在按下時添加綠色邊框,當擡起時,取消高亮  
    return new GestureDetector(
      onTapDown: _handleTapDown, // 處理按下事件
      onTapUp: _handleTapUp, // 處理擡起事件
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: new Container(
        child: new Center(
          child: new Text(widget.active ? 'Active' : 'Inactive',
              style: new TextStyle(fontSize: 32.0, color: Colors.white)),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? new Border.all(
                  color: Colors.teal[700],
                  width: 10.0,
                )
              : null,
        ),
      ),
    );
  }
}
複製代碼

擴展:全局狀態管理

當應用中包括一些跨Widget(甚至跨路由)的狀態須要同步時,上面介紹的方法就很難勝任了。好比,有一個設置頁,裏面能夠設置應用語言,可是爲了讓設置實時生效,咱們指望在語言狀態發生改變時,APP Widget可以從新構建(build),可是APP Widget和設置頁並不在一塊兒,那怎麼辦呢?正確的作法是經過一個全局狀態管理器來處理這種「相距較遠」的Widget之間的通訊。

目前只要有兩種方法:

  1. 實現一個全局的事件總線,將語言狀態改變對應爲一個事件,而後在APP Widget所在的父Widget initState()方法中訂閱語言狀態改變的事件,當用戶在設置頁切換語言狀態後,觸發語言狀態改變事件,而後APP Widget就會收到通知,接着從新構建(build)一下便可。
  2. 使用redux的全局狀態包,讀者能夠在pub上查看其詳細信息。

關於全局事件總線的實現,咱們後續進行。

相關文章
相關標籤/搜索