Flutter 基礎組件:狀態管理


前言

一個永恆的主題,“狀態(State)管理”,無論是在React/Vue(兩者都是支持響應式編程的Web開發框架)還是Flutter中,他們討論的問題和解決的思想都是一致的。
一個問題,StatefulWidget的狀態應該被誰管理?Widget本身?父Widget?都會?還是另一個對象?答案是取決於實際情況!以下是管理狀態的最常見的方法

  1. Widget管理自己的狀態。
  2. Widget管理子Widget狀態。
  3. 混合管理(父Widget和子Widget都管理狀態)。

如何決定使用哪種管理方法?下面是官方給出的一些原則

  1. 如果狀態是用戶數據,如復選框的選中狀態、滑塊的位置,則該狀態最好由父Widget管理。
  2. 如果狀態是有關界面外觀效果的,例如顏色、動畫,那么狀態最好由Widget本身來管理。
  3. 如果某一個狀態是不同Widget共享的則最好由它們共同的父Widget管理。

三個例子

下面通過三個例子來分別說明這三種管理方式。

Widget管理自身狀態

_TapBoxAState 類:

  • TapBoxA。
  • 定義_active:確定盒子的當前顏色的布爾值。
  • 定義_handleTap()函數,該函數在點擊該盒子時更新_active,並調用setState()更新UI。
  • 實現widget的所有交互式行為。
class TapBoxA extends StatefulWidget {
  TapBoxA({Key key}) : super(key: key);

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

class _TapBoxAState extends State<TapBoxA> {
  bool _active = false;

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

  Widget build(BuildContext context) {
    // 使用GestureDetector來識別點擊事件
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[700],
        ),
      ),
    );
  }
}

父Widget管理子Widget的狀態

對於父Widget來說,管理狀態並告訴其子Widget何時更新通常是比較好的方式。
例如,IconButton是一個圖標按鈕,但它是一個無狀態的Widget,因為我們認為父Widget需要知道該按鈕是否被點擊來采取相應的處理。

ParentWidgetState 類:

  1. 為TapBoxB 管理_active狀態。
  2. 實現_handleTaPBoxChanged(),當盒子被點擊時調用的方法。
  3. 當狀態改變時,調用setState()更新UI。
    TapBoxB 類:
  4. 繼承StatelessWidget類,因為所有狀態都由其父組件處理。
  5. 當檢測到點擊時,它會通知父組件。

TaPBoxB通過回調將其狀態導出到其父組件,狀態由父組件管理,因此它的父組件為StatefulWidget。
但是由於TapBoxB不管理任何狀態,所以TapBoxB為StatelessWidget。

class ParentWidgetB extends StatefulWidget {
  @override
  _ParentWidgetBState createState() => _ParentWidgetBState();
}

class _ParentWidgetBState extends State<ParentWidgetB> {
  bool _active = false;

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

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TapBoxB(
        active: _active,
        onChanged: _handleTaPBoxChanged,
      ),
    );
  }

}

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);
  }

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

混合狀態管理

TapBoxC示例中,手指按下時,盒子的周圍會出現一個深綠色的邊框,抬起時,邊框消失。
點擊完成后,盒子的顏色改變。TapBoxC將其_active狀態導出到其父組件中,但在內部管理其_highlight狀態。
這個例子有兩個狀態對象_ParentWidgetState和_TaPBoxCState。

_ParentWidgetStateC類:

  1. 管理_active 狀態。
  2. 實現 _handleTaPBoxChanged() ,當盒子被點擊時調用。
  3. 當點擊盒子並且_active狀態改變時調用setState()更新UI。

_TapBoxCState 對象:

  1. 管理_highlight 狀態。
  2. GestureDetector監聽所有tap事件。當用戶點下時,它添加高亮(深綠色邊框);當用戶釋放時,會移除高亮。
  3. 當按下、抬起、或者取消點擊時更新_highlight狀態,調用setState()更新UI。
  4. 當點擊時,將狀態的改變傳遞給父組件。
class ParentWidgetC extends StatefulWidget {
  @override
  _ParentWidgetCState createState() => _ParentWidgetCState();
}

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

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

  @override
  Widget build(BuildContext context) {
    return Container(
      child: TapBoxC(active: _active, onChanged: _handleTapBoxChanged,),
    );
  }
}

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

  final bool active;
  final ValueChanged<bool> onChanged;

  _TapBoxCState createState() => _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 GestureDetector(
      onTapDown: _handleTapDown,  // 處理按下事件
      onTapUp: _handleTapUp,  // 處理抬起事件
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: Container(
        child: Center(
          child: Text(
              widget.active ? 'Active' : 'Inactive',
              style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: widget.active ? Colors.lightGreen[200] : Colors.grey[200],
          border: _highlight ? Border.all(color: Colors.teal[700], width: 10.0,) : null,
        ),
      ),
    );
  }
}

全局狀態管理

當應用中需要一些跨組件(包括跨路由)的狀態需要同步時,上面三種管理方式就很難勝任了。
有一個設置頁,里面可以設置應用的語言,為了讓設置實時生效,期望在語言狀態發生改變時,APP中依賴應用語言的組件能夠重新build一下,但這些依賴應用語言的組件和設置頁並不在一起。
而全局狀態管理則可以處理這種相距較遠的組件之間的通信,目前有兩種方法:

  1. 實現一個全局的事件總線,將語言狀態改變為一個事件,然后在App中依賴應用語言的組件的initState方法中訂閱語言改變的事件。
    當用戶設置頁切換語言后,發布語言改變事件,而訂閱了此事件的組件就會收到通知,收到通知后調用setState()重新build一下即可。
  2. 使用一些專門用於狀態管理的包,如Provider、Redux等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM