3.Flutte3.0 遙遙領(lǐng)先系列|一文教你完全掌握狀態(tài)管理, 手寫簡(jiǎn)單版provider

重中之重, 源碼解析: (state的demo和源碼分析重點(diǎn)看看)

目錄

  1. StatelessWidget 與 StatefulWidget 區(qū)別? build方法
  2. 原始setstate的原理,狀態(tài)管理的重要性 (重點(diǎn))
  3. 官方 provider的原理解析 (重點(diǎn))
    3.1重點(diǎn)的2種方式和源碼分析 , (provider和InheritedWidget)
    3.2Provider的案例使用ChangeNotifier, ChangeNotifierProvider 實(shí)現(xiàn)2個(gè)組件之間的通信
    3.3Consumer 刷新指定區(qū)域
    3.4 Selector的用法
    3.5MultiProvider 多zhuang狀態(tài)共享.
    事實(shí)上,當(dāng)我們使用 Provider 后,我們就再也不需要使用 StatefulWidget 了。
  4. 手寫簡(jiǎn)單版provider
  5. 基礎(chǔ)的跨組件框架, InheritedWidget、Notification和EventBus
  6. 第三方框架圖文比較, 咸魚Fish Redux的框架 , getX的狀態(tài)管理

狀態(tài)管理是什么:

jietu-1706527334684.jpg

Flutter的狀態(tài)可以分為全局狀態(tài)和局部狀態(tài)兩種。

Flutter 狀態(tài)管理是指在 Flutter 應(yīng)用中有效地管理應(yīng)用的數(shù)據(jù)和狀態(tài)

狀態(tài)管理是聲明式編程非常重要的一個(gè)概念

問題: 為什么要做狀態(tài)管理?

就是有幾個(gè)頁(yè)面, 要實(shí)現(xiàn)數(shù)據(jù)的同步或者共享!

下面是官方給出的一些原則可以幫助你做決定:

  • 如果狀態(tài)是用戶數(shù)據(jù),如復(fù)選框的選中狀態(tài)、滑塊的位置,則該狀態(tài)最好由父 Widget 管理。
  • 如果狀態(tài)是有關(guān)界面外觀效果的,例如顏色、動(dòng)畫,那么狀態(tài)最好由 Widget 本身來(lái)管理。
  • 如果某一個(gè)狀態(tài)是不同 Widget 共享的則最好由它們共同的父 Widget 管理。

1. StatelessWidget 與 StatefulWidget 區(qū)別?

StatelessWidget和StatefulWidget的區(qū)別就在這個(gè)可變的State了。

當(dāng)狀態(tài)數(shù)據(jù)發(fā)生變化時(shí),F(xiàn)lutter 會(huì)調(diào)用 build() 方法重新構(gòu)建界面

問題: build方法什么情況下被執(zhí)行呢?:

  • 1)、當(dāng)我們的StatelessWidget第一次被插入到Widget樹中時(shí)(也就是第一次被創(chuàng)建時(shí));
  • 2)、當(dāng)我們的父Widget(parent widget)發(fā)生改變時(shí),子Widget會(huì)被重新構(gòu)建;
  • 3)、如果我們的Widget依賴InheritedWidget的一些數(shù)據(jù),InheritedWidget數(shù)據(jù)發(fā)生改變時(shí);

Stateful widget特有:

至少由兩個(gè)類組成:

 一個(gè)StatefulWidget類。

 一個(gè) State類; StatefulWidget類本身是不變的,但是State類中持有的狀態(tài)在 widget 生命周期中可能會(huì)發(fā)生變化。

問題: 為什么要將 build 方法放在 State 中,而不是放在StatefulWidget中?

1).狀態(tài)訪問不便, 屬性會(huì)被公開

試想一下,如果我們的StatefulWidget有很多狀態(tài),而每次狀態(tài)改變都要調(diào)用build方法,由于狀態(tài)是保存在 State 中的,如果build方法在StatefulWidget中,那么build方法和狀態(tài)分別在兩個(gè)類中,那么構(gòu)建時(shí)讀取狀態(tài)將會(huì)很不方便!

試想一下,如果真的將build方法放在 StatefulWidget 中的話,由于構(gòu)建用戶界面過(guò)程需要依賴 State,所以build方法將必須加一個(gè)State參數(shù),大概是下面這樣:

  Widget build(BuildContext context, State state){
      //state.counter
      ...
  }

這樣的話就只能將State的所有狀態(tài)聲明為公開的狀態(tài),這樣才能在State類外部訪問狀態(tài)!但是,將狀態(tài)設(shè)置為公開后,狀態(tài)將不再具有私密性,這就會(huì)導(dǎo)致對(duì)狀態(tài)的修改將會(huì)變的不可控。但如果將build()方法放在State中的話,構(gòu)建過(guò)程不僅可以直接訪問狀態(tài),而且也無(wú)需公開私有狀態(tài),這會(huì)非常方便。

2.繼承StatefulWidget不便。

例如,F(xiàn)lutter 中有一個(gè)動(dòng)畫 widget 的基類AnimatedWidget,它繼承自StatefulWidget類。AnimatedWidget中引入了一個(gè)抽象方法build(BuildContext context),繼承自AnimatedWidget的動(dòng)畫 widget 都要實(shí)現(xiàn)這個(gè)build方法?,F(xiàn)在設(shè)想一下,如果StatefulWidget 類中已經(jīng)有了一個(gè)build方法,正如上面所述,此時(shí)build方法需要接收一個(gè) State 對(duì)象,這就意味著AnimatedWidget必須將自己的 State 對(duì)象(記為_animatedWidgetState)提供給其子類,因?yàn)樽宇愋枰谄鋌uild方法中調(diào)用父類的build方法,代碼可能如下:

問題: 為什么flutter在設(shè)計(jì)的時(shí)候statefulWidget的build方法放在state中?

  1. build依賴state中的變量
    2.widget會(huì)不停的銷毀
  2. 專題改變, 不希望把state改變

問題: build的context是什么

在StatelessElement中,我們發(fā)現(xiàn)是將this傳入,所以本質(zhì)上BuildContext就是當(dāng)前的Element
context是當(dāng)前 widget 在 widget 樹中位置中執(zhí)行”相關(guān)操作“的一個(gè)句柄(handle),比如它提供了從當(dāng)前 widget 開始向上遍歷 widget 樹以及按照 widget 類型查找父級(jí) widget 的方法

class ContextRoute extends StatelessWidget  {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Context測(cè)試"),
      ),
      body: Container(
        child: Builder(builder: (context) {
          // 在 widget 樹中向上查找最近的父級(jí)`Scaffold`  widget 
          Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
          // 直接返回 AppBar的title, 此處實(shí)際上是Text("Context測(cè)試")
          return (scaffold.appBar as AppBar).title;
        }),
      ),
    );
 // 查找父級(jí)最近的Scaffold對(duì)應(yīng)的ScaffoldState對(duì)象
                  ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;

如果 StatefulWidget 的狀態(tài)是希望暴露出的,應(yīng)當(dāng)在 StatefulWidget 中提供一個(gè)of 靜態(tài)方法來(lái)獲取其 State 對(duì)象,開發(fā)者便可直接通過(guò)該方法來(lái)獲取;如果 State不希望暴露,則不提供of方法

  // 直接通過(guò)of靜態(tài)方法來(lái)獲取ScaffoldState
      ScaffoldState _state=Scaffold.of(context);

StatelessWidget特有
問題: 我之前說(shuō)過(guò)定義到Widget中的數(shù)據(jù)都是不可變的,必須定義為final,為什么呢?
Flutter如何做到我們?cè)陂_發(fā)中定義到Widget中的數(shù)據(jù)一定是final的呢?

@immutable
abstract class Widget extends DiagnosticableTree {
    // ...省略代碼
}

這里有一個(gè)很關(guān)鍵的東西@immutable

  • 我們似乎在Dart中沒有見過(guò)這種語(yǔ)法,這實(shí)際上是一個(gè) 注解,這設(shè)計(jì)到Dart的元編程,我們這里不展開講;

  • 來(lái)源: https://api.flutter.dev/flutt...

  • 說(shuō)明: 被@immutable注解標(biāo)明的類或者子類都必須是不可變的

2. 原始setstate的原理,狀態(tài)管理的重要性

源碼分析:

setState 僅在本地范圍內(nèi)有效,如果一個(gè) Widget 需要改變它自己的狀態(tài),那么 setState 就是你最好的選擇

setstate() 主要用于修改數(shù)據(jù),變量值的! 相當(dāng)于notifychange

問題: 如何從statefullWidget把數(shù)據(jù)傳遞到state類中?

可以2次傳遞

還有一種方法,state中可以直接取到statefullWidget的實(shí)例_widget , 就可以!

之前最簡(jiǎn)單的就是 widget, setstate

那 State 是在哪里被創(chuàng)建的?

class StatefulElement extends ComponentElement {
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    assert(() {
      if (!state._debugTypesAreRight(widget)) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
          ErrorDescription(
            'The createState function for ${widget.runtimeType} returned a state '
            'of type ${state.runtimeType}, which is not a subtype of '
            'State<${widget.runtimeType}>, violating the contract for createState.',
          ),
        ]);
      }
      return true;
    }());
    assert(state._element == null);
    state._element = this;
    state._widget = widget;
    assert(state._debugLifecycleState == _StateLifecycle.created);
  }

更新ui, state是啥?

  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget as ProxyWidget;
    assert(widget != newWidget);
    super.update(newWidget);
    assert(widget == newWidget);
    updated(oldWidget);
    rebuild(force: true);
  }

調(diào)用widget的setstate方法沒, 會(huì)執(zhí)行StatefulElement的update()方法

  @protected
  void setState(VoidCallback fn) {
    assert(() {
    final Object? result = fn() as dynamic;
    }());
    _element!.markNeedsBuild();  // markNeedsBuild () 
  }

 void markNeedsBuild() {
    assert(_lifecycleState != _ElementLifecycle.defunct);
    if (_lifecycleState != _ElementLifecycle.active) {
      return;
    }
    assert(owner != null);
    assert(_lifecycleState == _ElementLifecycle.active);
    assert(() {
      if (owner!._debugBuilding) {
        assert(owner!._debugCurrentBuildTarget != null);
        assert(owner!._debugStateLocked);
        if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) {
          return true;
        }
        final List<DiagnosticsNode> information = <DiagnosticsNode>[
          ErrorSummary('setState() or markNeedsBuild() called during build.'),
          ErrorDescription(
          ),
          describeElement('The widget on which setState() or markNeedsBuild() was called was'),
        ];
      return true;
    }());
    if (dirty) { // 這里進(jìn)行了返回! 
      return;
    }
    _dirty = true;
    owner!.scheduleBuildFor(this);  // 調(diào)用scheduleBuildFor方法, 傳入當(dāng)前Element對(duì)象
  }
  void scheduleBuildFor(Element element) {
    assert(element.owner == this);
    assert(() {
      if (debugPrintScheduleBuildForStacks) {
        debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}');
      }
      return true;
    }());
    if (element._inDirtyList) {
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);  //添加到_dirtyElements集合中
    element._inDirtyList = true;
      return true;
    }());
  }

從現(xiàn)象推斷,整個(gè)流程必然會(huì)經(jīng)過(guò)setState()-···················->當(dāng)前State的build()-················->頁(yè)面繪制-············->屏幕刷新
問題: setState ()是如何更新UI的?
setState源碼分析總結(jié):

我們常說(shuō)的 setState ,其實(shí)是調(diào)用了 markNeedsBuild ,markNeedsBuild 內(nèi)部會(huì)標(biāo)記 element 為 diry,添加到BuildOwner對(duì)象的_dirtyElements集合中, 然后調(diào)用scheduleFrame來(lái)注冊(cè)Vsync回調(diào)。 當(dāng)下一次vsync信號(hào)的到來(lái)時(shí)會(huì)執(zhí)行handleBeginFrame()和handleDrawFrame()來(lái)更新UI。
然后在下一幀 WidgetsBinding.drawFrame 才會(huì)被繪制,這可以也看出 setState 并不是立即生效的

State#setState 的核心作用就是把持有的元素標(biāo)臟并申請(qǐng)新幀調(diào)度。而只有新幀到來(lái),執(zhí)行完構(gòu)建之后,元素的 dirty 才會(huì)置為 false 。也就是說(shuō),兩幀之間,無(wú)論調(diào)用多少次 setState ,都只會(huì)觸發(fā)一次, 元素標(biāo)臟 和 申請(qǐng)新幀調(diào)度 。這就是為什么連續(xù)觸發(fā) 1000000 次,并無(wú)大事發(fā)生的原因

setState()會(huì)重建, 但是有dirty的判讀, 不會(huì)經(jīng)常重建!

dirty state的含義是臟的State
它實(shí)際是通過(guò)一個(gè)Element的東西(我們還沒有講到Flutter繪制原理)的屬性來(lái)標(biāo)記的;
將它標(biāo)記為dirty會(huì)等待下一次的重繪檢查,強(qiáng)制調(diào)用build方法來(lái)構(gòu)建我們的Widget
setState可以分為兩個(gè)部分:

將element標(biāo)臟
渲染時(shí)將所有臟element都rebuild,且將自己的child進(jìn)行update
重要的方法: performRebuild()

updateChild(_child, built, slot)

問題: setState每次都會(huì)去執(zhí)行build ()?

父widget
父widget2
子widget3: 用state變量

點(diǎn)擊: 調(diào)用setstate方法, 然后會(huì)創(chuàng)建, 調(diào)用 build(). 是不是只重繪制子widget3, 其他的不會(huì)重新繪制
setState 觸發(fā)了對(duì)你當(dāng)前所在的小組件的重建。如果你的整個(gè)應(yīng)用程序只包含一個(gè)widget,那么整個(gè)widget將被重建,這將使你的應(yīng)用程序變得緩慢
要把setstate多封裝一層, 讓setstate咋你自己的widget, 這樣就不會(huì)重建整個(gè)widget

問題: 為什么高位置的setState ()會(huì)消耗性能?

雖然setState的調(diào)用并沒有像 Widget 層那樣,在渲染控制層的 Element 那一層重新構(gòu)建全部element。但是,這并不代表 setState 的使用沒問題,首先,像之前篇章說(shuō)的那樣,它會(huì)重新構(gòu)建整個(gè) Widget 樹,這會(huì)帶來(lái)性能損耗;其次,由于整個(gè) Widget 樹改變了,意味著整棵樹對(duì)應(yīng)的渲染層Element對(duì)象都會(huì)執(zhí)行 update方法,雖然不一定會(huì)重新渲染,但是這整棵樹的遍歷的性能開銷也很高

總結(jié): 雖然每次不會(huì)都創(chuàng)建element, 但是遍歷有損耗, 提升辦法: 只更新需要更新的widget!
當(dāng)我們?cè)谝粋€(gè)高節(jié)點(diǎn)調(diào)用setState()的時(shí)候會(huì)構(gòu)建再次build所有的Widget,雖然不一定掛載到Element樹中,但是平時(shí)我們使用的Widget中往往嵌套多個(gè)其他類型的Widget,每個(gè)build()方法走下來(lái)最終也會(huì)帶來(lái)不小的開銷,因此通過(guò)各種狀態(tài)管理方案,Stream等方式,只做局部刷新,是我們?nèi)粘i_發(fā)中應(yīng)該養(yǎng)成的良好習(xí)慣。

流轉(zhuǎn)圖:
setState() 添加到_dirtyElements集合中, owner.scheduleBuildFor(this)
rebuild()
StatefulElement-->performRebuild(): 最重要的方法
StatefulElement--->updateChild()
Element---> update();

build()過(guò)程雖然只是調(diào)用一個(gè)組件的構(gòu)造方法,不涉及對(duì)Element樹的掛載操作。

但因?yàn)槲覀円粋€(gè)組件往往是N多個(gè)Widget的嵌套組合,每個(gè)都遍歷一遍開銷算下來(lái)并不小

問題: setState()如何做優(yōu)化?

要在子控件中調(diào)用setState(),

局部刷新 , 但是局部刷新,也只是 setState 的封裝

setstate.jpg

3. 官方 provider: 復(fù)雜情況的狀態(tài)管理

**數(shù)據(jù)傳遞: 多個(gè)頁(yè)面之間的傳遞, 數(shù)據(jù)變化通知! **

Flutter 官方的狀態(tài)管理框架 Provider 則相對(duì)簡(jiǎn)單得多

Provider 是一個(gè)用來(lái)提供數(shù)據(jù)的框架。它是 InheritedWidget 的語(yǔ)法糖,提供了依賴注入的功能,允許在 Widget 樹中更加靈活地處理和傳遞數(shù)據(jù)

Provider 就是針對(duì) InheritedWidget 的一個(gè)包裝工具

在使用Provider的時(shí)候,我們主要關(guān)心三個(gè)概念:

  • ChangeNotifier:真正數(shù)據(jù)(狀態(tài))存放的地方
  • ChangeNotifierProvider:Widget樹中提供數(shù)據(jù)(狀態(tài))的地方,會(huì)在其中創(chuàng)建對(duì)應(yīng)的ChangeNotifier
  • Consumer:Widget樹中需要使用數(shù)據(jù)(狀態(tài))的地方 讀Provider

讀和寫的Provider
ChangeNotifierProvider:Widget樹中提供數(shù)據(jù)(狀態(tài))的地方,會(huì)在其中創(chuàng)建對(duì)應(yīng)的ChangeNotifier
但是,濫用 Provider.of 方法也有副作用,那就是當(dāng)數(shù)據(jù)更新時(shí),頁(yè)面中其他的子 Widget 也會(huì)跟著一起刷新。
可以看到,TestIcon 控件本來(lái)是一個(gè)不需要刷新的 StatelessWidget,但卻因?yàn)槠涓?Widget FloatingActionButton 所依賴的數(shù)據(jù)資源 counter 發(fā)生了變化,導(dǎo)致它也要跟著刷新。

Consumer: 消費(fèi), 把數(shù)據(jù)消費(fèi)! (用的比 Provider.of 多)

因?yàn)镃onsumer在刷新整個(gè)Widget樹時(shí),會(huì)盡可能少的rebuild Widget。
在數(shù)據(jù)資源發(fā)生變化時(shí),只刷新對(duì)資源存在依賴關(guān)系的 Widget,而其他 Widget 保持不變呢?
有一個(gè)child().不重新構(gòu)建
Consumer是否是最好的選擇呢?并不是,它也會(huì)存在弊端 Selector的選擇 (使用的比較多)

比如當(dāng)點(diǎn)擊了floatingActionButton時(shí),我們?cè)诖a的兩處分別打印它們的builder是否會(huì)重新調(diào)用;
我們會(huì)發(fā)現(xiàn)只要點(diǎn)擊了floatingActionButton,兩個(gè)位置都會(huì)被重新builder;
但是floatingActionButton的位置有重新build的必要嗎?沒有,因?yàn)樗欠裨诓僮鲾?shù)據(jù),并沒有展示;

如何可以做到讓它不要重新build了?使用Selector來(lái)代替Consumer

所以在某些情況下,我們可以使用Selector來(lái)代替Consumer,性能會(huì)更高
Selector和Consumer對(duì)比,不同之處主要是三個(gè)關(guān)鍵點(diǎn):

關(guān)鍵點(diǎn)1:泛型參數(shù)是兩個(gè)
泛型參數(shù)一:我們這次要使用的Provider
泛型參數(shù)二:轉(zhuǎn)換之后的數(shù)據(jù)類型,比如我這里轉(zhuǎn)換之后依然是使用CounterProvider,那么他們兩個(gè)就是一樣的類型
關(guān)鍵點(diǎn)2:selector回調(diào)函數(shù)
轉(zhuǎn)換的回調(diào)函數(shù),你希望如何進(jìn)行轉(zhuǎn)換
S Function(BuildContext, A) selector
我這里沒有進(jìn)行轉(zhuǎn)換,所以直接將A實(shí)例返回即可
關(guān)鍵點(diǎn)3:是否希望重新rebuild
這里也是一個(gè)回調(diào)函數(shù),我們可以拿到轉(zhuǎn)換前后的兩個(gè)實(shí)例;
bool Function(T previous, T next);
因?yàn)檫@里我不希望它重新rebuild,無(wú)論數(shù)據(jù)如何變化,所以這里我直接return false;
多狀態(tài)的資源封裝

Provider 的另一個(gè)升級(jí)版 MultiProvider,來(lái)實(shí)現(xiàn)多個(gè) Provider 的組合注入
ScrollAwareImageProvider:
ChangeNotifier:
搞清楚TextField是怎么使用ChangeNotifier的了,為什么每次改變TextEditingController的text值,然后在TextField數(shù)據(jù)框里的數(shù)據(jù)也及時(shí)改變了,其實(shí)最后還是用到setState。
原理:
performRebuild() :該回調(diào)會(huì)在setState或者build的時(shí)候會(huì)觸發(fā);此處做了一個(gè)判斷,只會(huì)在第一次build的時(shí)候觸發(fā) build()
notifyDependent()

問題: InheritedElement? 而不是用普通的Element?

如Flutter SDK中正是通過(guò)InheritedWidget來(lái)共享應(yīng)用主題(Theme)和Locale (當(dāng)前語(yǔ)言環(huán)境)信息

InheritedWidget: 作為根節(jié)點(diǎn), 然后其他子節(jié)點(diǎn), 就可以獲取根節(jié)點(diǎn)的狀態(tài)! 但是如果要更新的話, 還是的手動(dòng)用setstate

最后,我們重寫了 updateShouldNotify 方法,這個(gè)方法會(huì)在 Flutter 判斷 InheritedWidget 是否需要重建,從而通知下層觀察者組件更新數(shù)據(jù)時(shí)被調(diào)用到。在這里,我們直接判斷 count 是否相等即可。

定義一個(gè)共享數(shù)據(jù)的InheritedWidget,需要繼承自InheritedWidget

  • 這里定義了一個(gè)of方法,該方法通過(guò)context開始去查找祖先的HYDataWidget(可以查看源碼查找過(guò)程)
  • updateShouldNotify方法是對(duì)比新舊HYDataWidget,是否需要對(duì)更新相關(guān)依賴的Widget

InheritedWidget中的屬性在子Widget中只能讀,如果有修改的場(chǎng)景,我們需要把它和StatefulWidget中的State配套使用。

InheritedWidget是Flutter中的一個(gè)功能型Widget,適用于在Widget樹中共享數(shù)據(jù)的場(chǎng)景。通過(guò)它,我們可以高效地將數(shù)據(jù)在Widget樹中進(jìn)行跨層傳遞

InheritedWidget使用方法

可以看到InheritedWidget的使用方法還是比較簡(jiǎn)單的,無(wú)論Counter在CountContainer下層什么位置,都能獲取到其父Widget的計(jì)數(shù)屬性count,再也不用手動(dòng)傳遞屬性了。

不過(guò),InheritedWidget僅提供了數(shù)據(jù)讀的能力,如果我們想要修改它的數(shù)據(jù),則需要把它和StatefulWidget中的State配套使用

使用場(chǎng)景:

InheritedWidget的數(shù)據(jù)流動(dòng)方式是從父Widget到子Widget逐層傳遞

InheritedWidget共有兩個(gè)方法

1).createElement() (創(chuàng)建對(duì)應(yīng)的Element)

2).updateShouldNotify(covariant InheritedWidget oldWidget)

問題: Flutter中的InheritedWidget的實(shí)現(xiàn)原理是怎么樣的?
InheritedWidget的原理:

主要是觀察模式的思想

源碼分析: (重點(diǎn))

class CountContainer extends InheritedWidget {
  // 方便其子 Widget 在 Widget 樹中找到它
  static CountContainer of(BuildContext context) => context.inheritFromWidgetOfExactType(CountContainer) as CountContainer;
  
  final int count;
 
  CountContainer({
    Key key,
    @required this.count,
    @required Widget child,
  }): super(key: key, child: child);
 
  // 判斷是否需要更新
  @override
  bool updateShouldNotify(CountContainer oldWidget) => count != oldWidget.count;
}

Provider 特點(diǎn):
因?yàn)?Provider 實(shí)際上是 InheritedWidget 的語(yǔ)法糖,所以通過(guò) Provider 傳遞的數(shù)據(jù)從數(shù)據(jù)流動(dòng)方向來(lái)看,是由父到子(或者反過(guò)來(lái))。這時(shí)我們就明白了,原來(lái)需要把資源放到 FirstPage 和 SecondPage 的父 Widget,也就是應(yīng)用程序的實(shí)例 MyApp 中(當(dāng)然,把資源放到更高的層級(jí)也是可以的,比如放到 main 函數(shù)中)

他最主要的功能:就是會(huì)調(diào)用Element的performRebuild()方法,然后觸發(fā)ComponentElement的build()方法,最終觸發(fā)_InheritedProviderScopeElement的build方法

其他: markNeedsNotifyDependents, 我們使用 notifyListeners(),就會(huì)觸發(fā),這個(gè)回調(diào)
provide不是調(diào)用的setstate()進(jìn)行狀態(tài)管理的么?

那怎么會(huì)觸發(fā)到performRebuild()這個(gè)方法了?

當(dāng)我們執(zhí)行 ChangeNotifer 的 notifyListeners 時(shí),就會(huì)最終觸發(fā) setState 更新。
執(zhí)行流程。
ChangeNotifer------>notifyListeners
setState()
InheritedElement------>performRebuild
InheritedElement------>build
InheritedElement------>notifyListeners

我們要知道一個(gè)前提:刷新Widget會(huì)先進(jìn)入Element的rebuild方法。然后是performRebuild方法,這個(gè)方法Element沒做什么,交由具體子類去實(shí)現(xiàn)
provider代碼原理總結(jié):

wiget是InheritedWidget

1)、 Provider 的內(nèi)部 DelegateWidget 是一個(gè) StatefulWidget ,所以可以更新且具有生命周期。
2)、狀態(tài)共享是使用了 InheritedProvider 這個(gè) InheritedWidget 實(shí)現(xiàn)的。

4.手寫簡(jiǎn)單版provider

使用
view

class CounterEasyPPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierEasyP(
      create: (BuildContext context) => CounterEasyP(),
      builder: (context) => _buildPage(context),
    );
  }

  Widget _buildPage(BuildContext context) {
    final easyP = EasyP.of<CounterEasyP>(context);

    return Scaffold(
      appBar: AppBar(title: Text('自定義狀態(tài)管理框架-EasyP范例')),
      body: Center(
        child: EasyPBuilder<CounterEasyP>(() {
          return Text(
            '點(diǎn)擊了 ${easyP.count} 次',
            style: TextStyle(fontSize: 30.0),
          );
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => easyP.increment(),
        child: Icon(Icons.add),
      ),
    );

ChangeNotifier

class CounterEasyP extends ChangeNotifier {
  int count = 0;

  void increment() {
    count++;
    notifyListeners();
  }
}

核心類: 3個(gè)
ChangeNotifierProvider: 最重要的方法

class ChangeNotifierProvider<T extends ChangeNotifier> extends StatelessWidget {
  ChangeNotifierEasyP({
    Key? key,
    required this.create,
    this.builder,
    this.child,
  }) : super(key: key);

  final T Function(BuildContext context) create;

  final Widget Function(BuildContext context)? builder;
  final Widget? child;

@override
  Widget build(BuildContext context) {  // 重寫 build()方法, 返回InheritedWidget對(duì)象
    assert(
      builder != null || child != null,
      '$runtimeType  must specify a child',
    );

    return EasyPInherited(
      create: create,
      child: builder != null
          ? Builder(builder: (context) => builder!(context))
          : child!,
    );
  }
}
class EasyPInherited<T extends ChangeNotifier> extends InheritedWidget { // 實(shí)現(xiàn)一個(gè)InheritedWidget
  EasyPInherited({
    Key? key,
    required Widget child,
    required this.create,
  }) : super(key: key, child: child);

  final T Function(BuildContext context) create;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => false;  // 重寫updateShouldNotify方法

  @override
  InheritedElement createElement() => EasyPInheritedElement(this);
}

class EasyPInheritedElement<T extends ChangeNotifier> extends InheritedElement { // InheritedElement重寫
  EasyPInheritedElement(EasyPInherited<T> widget) : super(widget);

  bool _firstBuild = true;
  bool _shouldNotify = false;
  late T _value;
  late void Function() _callBack;

  T get value => _value;

  @override
  void performRebuild() { // 實(shí)現(xiàn)performRebuild()
  if (_firstBuild) {
      _firstBuild = false;
      _value = (widget as EasyPInherited<T>).create(this);

      _value.addListener(_callBack = () {
        // 處理刷新邏輯,此處無(wú)法直接調(diào)用notifyClients
        // 會(huì)導(dǎo)致owner!._debugCurrentBuildTarget為null,觸發(fā)斷言條件,無(wú)法向后執(zhí)行
        _shouldNotify = true;
        markNeedsBuild();
      });
    }

    super.performRebuild();
  }

  @override
  Widget build() {  // build()重寫
    if (_shouldNotify) {
      _shouldNotify = false;
      notifyClients(widget as EasyPInherited<T>);
    }
    return super.build();
  }

  @override
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    //此處就直接刷新添加的監(jiān)聽子Element了,不各種super了
    dependent.markNeedsBuild();
    // super.notifyDependent(oldWidget, dependent);
  }

  @override
  void unmount() {
    _value.removeListener(_callBack);
    _value.dispose();
    super.unmount();
  }
}

Provider

class Provider { // 不包含核心邏輯,僅僅是封裝 
  /// 獲取EasyP實(shí)例
  /// 獲取實(shí)例的時(shí)候,listener參數(shù)老是寫錯(cuò),這邊直接用倆個(gè)方法區(qū)分了
  static T of<T extends ChangeNotifier>(BuildContext context) {
    return _getInheritedElement<T>(context).value;
  }

  /// 注冊(cè)監(jiān)聽控件
  static T register<T extends ChangeNotifier>(BuildContext context) {
    var element = _getInheritedElement<T>(context);
    context.dependOnInheritedElement(element);
    return element.value;
  }

  /// 獲取距離當(dāng)前Element最近繼承InheritedElement<T>的組件
  //調(diào)用dependOnInheritedWidgetOfExactType() 和 getElementForInheritedWidgetOfExactType()的區(qū)別就是前者會(huì)注冊(cè)依賴關(guān)系,而后者不會(huì)
  static EasyPInheritedElement<T>
      _getInheritedElement<T extends ChangeNotifier>(BuildContext context) {
    var inheritedElement = context
            .getElementForInheritedWidgetOfExactType<EasyPInherited<T>>()
        as EasyPInheritedElement<T>?;

    if (inheritedElement == null) {
      throw EasyPNotFoundException(T);
    }

    return inheritedElement;
  }
}

class EasyPNotFoundException implements Exception {
  EasyPNotFoundException(this.valueType);

  final Type valueType;

  @override
  String toString() => 'Error: Could not find the EasyP<$valueType>';
}

builder :

class EasyPBuilder<T extends ChangeNotifier> extends StatelessWidget {
  const EasyPBuilder(
    this.builder, {
    Key? key,
  }) : super(key: key);

  final Widget Function() builder;

  @override
  Widget build(BuildContext context) {
    EasyP.register<T>(context);
    return builder();
  }
}

案例:

自己寫一個(gè)封裝的controller()

3個(gè)核心類:

build:

Provider:

ChangeNotifierProvider:

點(diǎn)擊事件: 數(shù)據(jù)變化調(diào)用provide里面的變化方法,

ui更新, 也是拿provider的值

 Widget _buildPage(BuildContext context) {
    final easyP = EasyP.of<CounterEasyP>(context);

    return Scaffold(
      appBar: AppBar(title: Text('自定義狀態(tài)管理框架-EasyP范例')),
      body: Center(
        child: EasyPBuilder<CounterEasyP>(() {
          return Text(
            '點(diǎn)擊了 ${easyP.count} 次',
            style: TextStyle(fontSize: 30.0),
          );
        }),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => easyP.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

創(chuàng)建了一個(gè)widget:

class CounterEasyPPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierEasyP(
      create: (BuildContext context) => CounterEasyP(),
      builder: (context) => _buildPage(context),
    );
  }

把需要更新的widget傳入到構(gòu)造方法中去了! ChangeNotifier傳入, buildcontext也在

5. 簡(jiǎn)單的狀態(tài)管理:

對(duì)于數(shù)據(jù)的跨層傳遞,F(xiàn)lutter還提供了三種方案:InheritedWidget、Notification和EventBus。接下來(lái),我將依次為你講解這三種方案。


b2a78dbefdf30895504b2017355ae066.png

5.1 InheritedWidget 原理如上

5.2 Notification

數(shù)據(jù)流動(dòng)方式是從子Widget向上傳遞至父Widget。這樣的數(shù)據(jù)傳遞機(jī)制適用于子Widget狀態(tài)變更,發(fā)送通知上報(bào)的場(chǎng)景

Notification是一種用于在小部件樹中傳遞信息的機(jī)制,它可以用于實(shí)現(xiàn)子樹中的特定部分之間的通信。Notification并不像狀態(tài)管理或全局狀態(tài)傳遞那樣普遍,它主要用于特定場(chǎng)景下的通信,比如當(dāng)某個(gè)事件發(fā)生時(shí),需要在小部件樹的各個(gè)部分之間傳遞消息。Notification的工作方式是通過(guò)Notification對(duì)象在小部件樹中傳遞,然后從父級(jí)小部件開始逐級(jí)向上冒泡,直到找到一個(gè)處理該通知的小部件為止。每個(gè)處理通知的小部件可以根據(jù)需要執(zhí)行特定的操作。你可以把InheritedWidget 理解為從上到下傳遞、共享的方式,而Notification則是從下往上。Notification它提供了dispatch方法,沿著context對(duì)應(yīng)的Element節(jié)點(diǎn)向上逐層發(fā)送通知。

跨組件事件傳遞

5.3 EventBus

在組件之間如果有事件需要傳遞,一方面可以一層層來(lái)傳遞,另一方面我們也可以使用一個(gè)EventBus工具來(lái)完成。

其實(shí)EventBus在Vue、React中都是一種非常常見的跨組件通信的方式:

EventBus相當(dāng)于是一種訂閱者模式,通過(guò)一個(gè)全局的對(duì)象來(lái)管理;

這個(gè)EventBus我們可以自己實(shí)現(xiàn),也可以使用第三方的EventBus;

    無(wú)需發(fā)布者與訂閱者之間存在父子關(guān)系的數(shù)據(jù)同步機(jī)制。

無(wú)論是InheritedWidget還是Notificaiton,它們的使用場(chǎng)景都需要依靠Widget樹,也就意味著只能在有父子關(guān)系的Widget之間進(jìn)行數(shù)據(jù)共享。但是,組件間數(shù)據(jù)傳遞還有一種常見場(chǎng)景:這些組件間不存在父子關(guān)系。這時(shí),事件總線EventBus就登場(chǎng)了。

     事件總線是在Flutter中實(shí)現(xiàn)跨組件通信的機(jī)制。它遵循發(fā)布/訂閱模式,允許訂閱者訂閱事件,當(dāng)發(fā)布者觸發(fā)事件時(shí),訂閱者和發(fā)布者之間可以通過(guò)事件進(jìn)行交互。發(fā)布者和訂閱者之間無(wú)需有父子關(guān)系,甚至非Widget對(duì)象也可以發(fā)布/訂閱。這些特點(diǎn)與其他平臺(tái)的事件總線機(jī)制是類似的。

     總結(jié):  

這里我準(zhǔn)備了一張表格,把屬性傳值、InheritedWidget、Notification與EventBus這四種數(shù)據(jù)共享方式的特點(diǎn)和使用場(chǎng)景做了簡(jiǎn)單總結(jié),供你參考:

6. 下面是對(duì)Provider、BLoC、Redux、GetX、Riverpod和MobX等Flutter狀態(tài)管理庫(kù)的一些對(duì)比:

狀態(tài)管理比較.jpg

`1). InheritedWidget:在 Flutter 中,所有 Widget 都是通過(guò)父 Widget 構(gòu)建出來(lái)的,父 Widget 可以通過(guò) InheritedWidget 共享狀態(tài)給子 Widget,子 Widget 可以通過(guò)調(diào)用 InheritedWidget.of() 方法來(lái)獲取共享的狀態(tài)。

2). Provider:

  • 適用場(chǎng)景: 適用于中小型應(yīng)用,需要在多個(gè)層級(jí)共享狀態(tài)的場(chǎng)景。
  • 特點(diǎn): 輕量級(jí),使用InheritedWidget來(lái)共享狀態(tài),支持各種類型的狀態(tài),易于上手。
  • 優(yōu)勢(shì): 簡(jiǎn)單易用,不需要大量的額外代碼,具有高性能,適用于簡(jiǎn)單的狀態(tài)共享。
  • 劣勢(shì): 在大型應(yīng)用中可能難以管理復(fù)雜的狀態(tài)。

狀態(tài)管理混亂,雖然用了 provider 來(lái)做狀態(tài)管理,但一些代碼如:異步請(qǐng)求、事件響應(yīng)等還是會(huì)摻雜在UI頁(yè)面的代碼里面,一旦頁(yè)面的各種 Widget 多了起來(lái)之后,顯得非常嚴(yán)重,而且對(duì)業(yè)務(wù)邏輯的測(cè)試也不方便,多個(gè)組件可能需要共享相同的數(shù)據(jù)或狀態(tài),需要在每個(gè)組件中分別創(chuàng)建 Provider 實(shí)例,容易導(dǎo)致代碼冗余,如果只需要更新頁(yè)面的部分 Widget 使用Provider 還會(huì)導(dǎo)致嵌套過(guò)深,也可能導(dǎo)致性能問題、狀態(tài)不一致以及難以追蹤的錯(cuò)誤

3). ScopedModel:ScopedModel 也可以實(shí)現(xiàn)狀態(tài)共享,但它的思想是將數(shù)據(jù)放在一個(gè)共享的 Model 中,然后讓需要用到這些數(shù)據(jù)的 Widget 注冊(cè)監(jiān)聽該 Model,當(dāng) Model 的數(shù)據(jù)改變時(shí),通知監(jiān)聽它的 Widget 更新。
4). Stream

Stream是一種用于在應(yīng)用程序中管理狀態(tài)和數(shù)據(jù)流的重要工具。Stream是異步數(shù)據(jù)流的抽象表示,它可以在應(yīng)用程序中傳遞和監(jiān)聽數(shù)據(jù)的變化。但是它和Flutter關(guān)系并不大,它是通過(guò)純dart去實(shí)現(xiàn)的。你可以理解為flutter只是通過(guò)StreamBuilder去構(gòu)建了一個(gè)Stream通道。它的使用其實(shí)也并沒有復(fù)雜太多,通常只需要?jiǎng)?chuàng)建StreamController,然后去監(jiān)聽控制器(可以直接去監(jiān)聽StreamController,然后通過(guò)setState更新UI,也可以通過(guò)StreamBuilder),最后將更新后的數(shù)據(jù)通過(guò)Stream的sink屬性添加到Stream中即可。知名的狀態(tài)管理庫(kù)Bloc,就是基于Stream的封裝。

5). BLoC:

BLoC 算是 Flutter 早期比較知名的狀態(tài)管理框架, 它是基于事件驅(qū)動(dòng)來(lái)實(shí)現(xiàn)的狀態(tài)管理

BLoC 是業(yè)務(wù)邏輯組件的縮寫,它使用 Streams 和Provider 庫(kù)將業(yè)務(wù)邏輯和 UI 分離開來(lái),可以用來(lái)管理狀態(tài)和處理用戶輸入。作者在這里使用了Bloc用于狀態(tài)管理

基于 Stream 的封裝可以更方便做一些事件狀態(tài)的監(jiān)聽和轉(zhuǎn)換

  • 適用場(chǎng)景: 適用于復(fù)雜的應(yīng)用,需要分離業(yè)務(wù)邏輯和UI的場(chǎng)景。
  • 特點(diǎn): 通過(guò)Streams管理狀態(tài)和業(yè)務(wù)邏輯,將界面層與業(yè)務(wù)邏輯層分開,適合中大型應(yīng)用。
  • 優(yōu)勢(shì): 適合處理復(fù)雜的狀態(tài)變化和異步操作,便于測(cè)試和維護(hù)。
  • 劣勢(shì): 在簡(jiǎn)單應(yīng)用中可能顯得過(guò)于復(fù)雜。需要寫更多的代碼,開發(fā)節(jié)奏會(huì)有點(diǎn)影響

6). Redux:

  • Redux 是一種狀態(tài)管理模式,它將狀態(tài)和狀態(tài)更新封裝在一個(gè)可預(yù)測(cè)的單向數(shù)據(jù)流中,可以用于處理應(yīng)用程序的復(fù)雜狀態(tài)。

前端開始者對(duì) redux 可能會(huì)更熟悉一些

在 flutter_redux 中,開發(fā)者的每個(gè)操作都只是一個(gè) Action ,而這個(gè)行為所觸發(fā)的邏輯完全由 middlewarereducer 決定

  • 適用場(chǎng)景: 適用于需要管理大量復(fù)雜狀態(tài)的應(yīng)用。
  • 特點(diǎn): 基于單一狀態(tài)源和不可變狀態(tài),通過(guò)Actions和Reducers來(lái)管理狀態(tài)變化。
  • 優(yōu)勢(shì): 嚴(yán)格的狀態(tài)管理,適用于大型應(yīng)用,具有強(qiáng)大的開發(fā)工具和中間件。
  • 劣勢(shì): 在小型應(yīng)用中可能過(guò)于繁瑣,學(xué)習(xí)曲線較陡`
    7). ;不在維護(hù)了2012年后沒有維護(hù)了! 第三方框架, 咸魚Fish Redux的框架

8). Riverpod:

適用場(chǎng)景: 適用于需要更強(qiáng)大、更簡(jiǎn)單的狀態(tài)管理和依賴注入的場(chǎng)景。
特點(diǎn): 基于Provider的升級(jí)版本,提供更簡(jiǎn)單、更強(qiáng)大的API,支持多種狀態(tài)管理模式。
優(yōu)勢(shì): 代碼清晰,性能高效,支持多種狀態(tài)管理模式,適用于各種規(guī)模的項(xiàng)目。
劣勢(shì): 相對(duì)較新的庫(kù),社區(qū)可能還在成長(zhǎng)。
隨著 Flutter 的發(fā)展,這些年 Flutter 上的狀態(tài)管理框架如“雨后春筍”般層出不窮,而近一年以來(lái)最受官方推薦的狀態(tài)管理框架無(wú)疑就是 Riverpod
直觀的就是 Riverpod 中的 Provider 可以隨意寫成全局, 如果說(shuō) Riverpod 最明顯的特點(diǎn)是什么,那就是外部不依賴 BuildContext
它的作者也是 Provider 的作者,同時(shí)也是 Flutter 官方推薦的狀態(tài)管理庫(kù)
如何實(shí)現(xiàn)不依賴 BuildContext?
在 Riverpod 里基本是每一個(gè) “Provider” 都會(huì)有一個(gè)自己的 “Element” ,然后通過(guò) WidgetRef 去 Hook 后成為 BuildContext 的替代,所以這就是 Riverpod 不依賴 Context 的 “魔法” 之一
9). GetX:

適用場(chǎng)景: 適用于快速開發(fā)和中小型應(yīng)用,需要輕量級(jí)狀態(tài)管理和依賴注入的場(chǎng)景。
特點(diǎn): 簡(jiǎn)單易用,提供狀態(tài)管理、依賴注入和路由導(dǎo)航的綜合解決方案。
優(yōu)勢(shì): 低學(xué)習(xí)曲線,高性能,適用于快速迭代的小型項(xiàng)目。
劣勢(shì): 對(duì)于大型復(fù)雜應(yīng)用,可能需要更復(fù)雜的狀態(tài)管理方案。

10). MobX:
適用場(chǎng)景: 適用于需要響應(yīng)式編程和可觀察對(duì)象的場(chǎng)景。
特點(diǎn): 通過(guò)可觀察對(duì)象和反應(yīng)式編程來(lái)管理狀態(tài),支持多種數(shù)據(jù)變化方式。提高開發(fā)效率
優(yōu)勢(shì): 簡(jiǎn)化了狀態(tài)管理,具有響應(yīng)式編程的特點(diǎn),易于學(xué)習(xí)和使用。
劣勢(shì): 相對(duì)較新的庫(kù),可能在一些大型項(xiàng)目中缺乏一些高級(jí)功能。全家桶,做的太多對(duì)于一些使用者來(lái)說(shuō)是致命缺點(diǎn),需要解決的 Bug 也多
11). rxdart: 太老了

狀態(tài)管理.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容