Flutter入門 - 狀態管理

這裡沒有賀先生發表於2021-08-31

概述

從指令式程式設計框架(iOS、Android)到宣告式程式設計(Flutter、vue), 比如 Flutter 有大量的 State 需要來進行管理,資料共享的時候,

分類

  • 短時狀態

某些簡單的資料變化,只需要使用 statefulWidget 的 state 類自己管理即可

  • 應用狀態AppState

比如使用者資訊,全域性性的資料資訊,需要選擇全域性狀態管理的方式

共享狀態管理

InheritedWidget

定義一個共享資料的 InheritedWidget,需要繼承自 InheritedWidget

  • 需要定義一個 of 靜態方法,通過傳入 context 開始去查詢祖先的資料
class ShareWidget extends InheritedWidget {
  //共享的 counter 資料
  final int counter;
  ShareWidget({this.counter, Widget child}) : super(child: child);
  
  static ShareWidget of(BuildContext context) {
    //沿著 context 往上查詢最近的祖先 widget
    return context.dependOnInheritedWidgetOfExactType();
  }

  @override
  bool updateShouldNotify(covariant ShareWidget oldWidget) {
    //是否需要通知資料變更
    return this.counter != oldWidget.counter;
  }
}
複製程式碼

其他 widget 使用

@override
Widget build(BuildContext context) {
  // 通過呼叫 of 靜態方法拿到 “ShareWidget”
  int _counter = ShareWidget.of(context).counter;
  return Container(
    child: Text("$_counter"),
  );
}
複製程式碼

Provider

官方推薦的全域性狀態管理工具

dependencies:
    provider:^4.0.4
複製程式碼

三個概念

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

Provider 的基本用法

1.1 建立 provider

繼承自 ChangeNotifier

class ViewModelProvider extends ChangeNotifier {
  // 1.建立自己想要共享的資料
  int _counter = 100;

  int get counter => _counter;

  set counter(int value) {
    _counter = value;
    notifyListeners();
  }
}
複製程式碼

1.2 插入 provider

在應用頂層放入 ChangeNotifierProvider, 這樣全域性都可以使用 ViewModelProvider

void main() {
  runApp(ChangeNotifierProvider(
      create: (ctx) => ViewModelProvider(), child: HomePage()));
}
複製程式碼

1.3 使用資料

1.3.1 Provider.of
int _counter = Provider.of<ViewModelProvider>(context).counter;
複製程式碼
1.3.2 Consumer
  • 使用 Consumer 包裹要使用共享資料的 widget
  • builder 會返回三個引數
    • context 返回的是當前樹的位置
    • provider ChangeNotifier對應的例項,可以直接使用 provider 中的資料
    • child 目的是防止 child 被重複 build
  • 當然使用 Consumer 的話,builder 所返回的 widget(例如demo中的FloatingActionButton) 仍然會被重複 build, 所以引入 Seletor
floatingActionButton: Consumer<ViewModelProvider>(
  child: Icon(Icons.add),
  //builder 會返回三個引數 context、provider、child
  builder: (cxt, provider, child) {
    return FloatingActionButton(
      child: child,
      onPressed: () {
        setState(() {
          provider.counter++;
        });
      },
    );
  },
),
複製程式碼
1.3.3 Selector

關鍵引數

  • 1.泛型引數<A,S>
    • A: 我們這次要使用的 provider
    • S: 轉換之後的資料型別
  • 2.seletor 回撥函式
    • 轉換的回撥函式,沒有轉換的話,可以直接返回 Provider 的例項
  • 3.是否需要 rebuild
Selector({
  Key? key,
  required ValueWidgetBuilder<S> builder,
  required S Function(BuildContext, A) selector,
  ShouldRebuild<S>? shouldRebuild,
  Widget? child,
})
複製程式碼
floatingActionButton: Selector<ViewModelProvider, ViewModelProvider>(
  selector: (cxt, provider) => provider,
  shouldRebuild: (pre, next) => false,
  builder: (cxt, provider, child) {
    print("Selector build");
    return FloatingActionButton(
      onPressed: () {
        provider.counter++;
      },
      child: child,
    );
  },
),
複製程式碼
1.3.4 MultiProvider

開發中肯定不止一個 provider,不可能全部巢狀在 runApp中,因此使用 MultiProvider

可以定義一個 dart 類專門存放 providers

List<SingleChildWidget> providers = [
  ChangeNotifierProvider(
    create: (ctx) => Provider1(),
  ),
  ChangeNotifierProvider(
    create: (ctx) => Provider2(),
  ),
];
複製程式碼
void main() {
  runApp(
    MultiProvider(
      // 使用
      providers: providers,
      child: HomePage(),
    ),
  );
}
複製程式碼

使用小結

* 1.建立自己想要共享的資料  Provider extends ChangeNotifier
* 2.在應用頂層修改 runApp 新增 ChangeNotifierProvider
* 3.在其他位置使用共享資料
*   1)Provider.of<ViewModelProvider>(context)
*   2) Consumer (相對推薦)
*   3) Selector
* 4.使用 MultiProvider 管理多個共享狀態
複製程式碼

相關文章