Flutter-狀態管理

1、什麼是狀態管理

大到整個app的狀態,用戶使用app是登陸狀態,仍是遊客狀態;小到一個按鈕的狀態,按鈕是點擊選中狀態仍是未點擊狀態等等,這些都是狀態管理。編程

2、命令式編程和聲明式編程狀態管理的區別

  • iOS是如何管理狀態的,通常都是獲取這個控件而後設置你想要的狀態
  • 當你的 Flutter 應用的狀態發生改變時(例如,用戶在設置界面中點擊了一個開關選項)你改變了狀態,這將會觸發用戶界面的重繪。去改變用戶界面自己是沒有必要的(例如 widget.setText )—你改變了狀態,那麼用戶界面將從新構建。

3、狀態管理中的聲明式編程思惟

Flutter 應用是 聲明式 的,這也就意味着 Flutter 構建的用戶界面就是應用的當前狀態。bash

一旦你的界面狀態發生改變,就會觸發界面的從新繪製,繪製出你想要的界面,而不是像iOS的OC語言那樣去獲取須要改變狀態的控件,而後修改它架構

4、短時 (ephemeral) 和應用 (app) 狀態的區別

Flutter中的狀態管理又分爲短時狀態和應用狀態。app

  • 短時狀態,就是在單個頁面須要保持的狀態,好比頁面數據加載到了第幾頁,關注按鈕是已關注仍是未關注等,都是在單個頁面須要保持的狀態。widget樹中其餘部分不須要訪問這種狀態。不須要去序列化這種狀態,這種狀態也不會以複雜的方式改變。換句話說,不須要使用狀態管理架構(例如 ScopedModel, Redux)去管理這種狀態。你須要用的只是一個 StatefulWidget。

在下方你能夠看到一個底部導航欄中當前被選中的項目是如何被被保存在 _MyHomepageState 類的 _index 變量中。在這個例子中,_index 是一個短時狀態。less

class MyHomepage extends StatefulWidget {
  @override
  _MyHomepageState createState() => _MyHomepageState();
}

class _MyHomepageState extends State<MyHomepage> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      currentIndex: _index,
      onTap: (newIndex) {
        setState(() {
          _index = newIndex;
        });
      },
      // ... items ...
    );
  }
}
複製代碼

在這裏,使用 setState() 和一個變量就能達到管理狀態的目的。你的 app 中的其餘部分不須要訪問 _index。這個變量只會在 MyHomepage widget 中改變。並且,若是用戶關閉並重啓這個 app,_index會被重置而不會繼續保持原來的狀態。ide

  • 應用狀態,若是你想在你的應用中的多個部分之間共享一個非短時的狀態,而且在用戶會話期間保留這個狀態,咱們稱之爲應用狀態(有時也稱共享狀態)。 應用狀態的一些例子:函數

    一、用戶選項
    
      二、登陸信息
    
      三、一個社交應用中的通知
    
      四、一個電商應用中的購物車
    
      五、一個新聞應用中的文章已讀/未讀狀態
    複製代碼

5、共享狀態管理

在 Flutter 中,通常是將存儲狀態的對象置於 widget 樹中對應 widget 的上層,當它發生改變的時候,它對應的widget會從上層開始重構。由於這個機制,因此 widget 無需考慮生命週期的問題—它只須要針對 上層存儲數據的對象 聲明所需顯示內容便可。當內容發生改變的時候,舊的 widget 就會消失,徹底被新的 widget 替代。 Flutter原生提供了兩個方法來管理共享狀態:測試

5.1 --InheritedWidget

class ADCounterWidget extends InheritedWidget {
  // 1. 共享的數據
  final int counter;

  // 2. 定義構造方法
  ADCounterWidget({this.counter, Widget child}): super(child: child);

  // 3. 找到當前Widget樹中最近的InheritedWidget
  static ADCounterWidget of(BuildContext context) {
    // 沿着Element樹, 去找到最近的ADCounterElement, 從Element中取出Widget對象
    return context.dependOnInheritedWidgetOfExactType();
  }

  // 4. 要不要回調State中的didChangeDependencies方法
  @override
  bool updateShouldNotify(ADCounterWidget oldWidget) {
    return oldWidget.counter != counter;
  }
}

複製代碼
  • 上面定義了一個of方法,該方法經過context開始去查找父級的HYDataWidget
  • updateShouldNotify方法是對比新舊HYDataWidget,是否須要對更新相關依賴的Widget
class HYHomePage extends StatefulWidget {
  @override
  _HYHomePageState createState() => _HYHomePageState();
}

class _HYHomePageState extends State<HYHomePage> {
  int data = 100;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedWidget"),
      ),
      body: HYDataWidget(
        counter: data,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              HYShowData()
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          setState(() {
            data++;
          });
        },
      ),
    );
  }
}

複製代碼

建立HYDataWidget,而且傳入數據(這裏點擊按鈕會修改數據,而且出發從新build)優化

5.2 --Provider

Provider庫有三個主要用到的類:ui

  • ChangeNotifier:真正數據(狀態)存放的地方
  • ChangeNotifierProvider:Widget樹中提供數據(狀態)的地方,會在其中建立對應的ChangeNotifier
  • Consumer:Widget樹中須要使用數據(狀態)的地方

第一步 在程序的最頂層建立本身的ChangeNotifier

  • 將ChangeNotifierProvider放到了頂層,這樣方便在整個應用的任何地方可使用CounterProvider
  • 在ChangeNotifier中建立一個私有的_counter,而且提供了getter和setter
  • 在setter中咱們監聽到_counter的改變,就調用notifyListeners方法,通知全部的Consumer進行更新
void main() {
  runApp(ChangeNotifierProvider(
    create: (context) => CounterProvider(),
    child: MyApp(),
  ));
}

class CounterProvider extends ChangeNotifier {
  int _counter = 100;
  intget counter {
    return _counter;
  }
  set counter(int value) {
    _counter = value;
    notifyListeners();
  }
}

複製代碼

第二步 在首頁中使用Consumer引入和修改狀態

  • 在body中使用Consumer,Consumer須要傳入一個builder回調函數,當數據發生變化時,就會通知依賴數據的Consumer從新調用builder方法來構建
  • 在floatingActionButton中使用Consumer,當點擊按鈕時,修改CounterNotifier中的counter數據
class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("列表測試"),
      ),
      body: Center(
        child: Consumer<CounterProvider>(
          builder: (ctx, counterPro, child) {
            return Text("當前計數:${counterPro.counter}", style: TextStyle(fontSize: 20, color: Colors.red),);
          }
        ),
      ),
      floatingActionButton: Consumer<CounterProvider>(
        builder: (ctx, counterPro, child) {
          return FloatingActionButton(
            child: child,
            onPressed: () {
              counterPro.counter += 1;
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

複製代碼

Consumer的builder方法有三個參數:

  • context,每一個build方法都會有上下文,目的是知道當前樹的位置
  • ChangeNotifier對應的實例,也是咱們在builder函數中主要使用的對象
  • child,目的是進行優化,若是builder下面有一顆龐大的子樹,當模型發生改變的時候,咱們並不但願從新build這顆子樹,那麼就能夠將這顆子樹放到Consumer的child中,在這裏直接引入便可(注意我案例中的Icon所放的位置)

第四步 建立一個新的頁面,在新的頁面中修改數據

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二個頁面"),
      ),
      floatingActionButton: Consumer<CounterProvider>(
        builder: (ctx, counterPro, child) {
          return FloatingActionButton(
            child: child,
            onPressed: () {
              counterPro.counter += 1;
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

複製代碼
相關文章
相關標籤/搜索