Riverpod狀態(tài)管理詳解-1

Riverpod數(shù)據(jù)共享也是使用了InheritedWidget,在項(xiàng)目中,runapp外層要嵌套一個(gè)ProviderScope

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

ProviderScope是個(gè)StatefulWidget,在State build方法中,使用了UncontrolledProviderScope

image.png
UncontrolledProviderScope就是一個(gè)InheritedWidget
image.png

在我們使用riverpod時(shí)候,獲取的狀態(tài)都保存在這個(gè)UncontrolledProviderScope中,具體是ProviderContainer對象當(dāng)中,這里能夠感受到的一個(gè)最大的優(yōu)勢就是不再依賴上下文,因?yàn)樵谌魏蔚胤将@取的都是這個(gè)頂層的狀態(tài),像之前如果路由進(jìn)行了跳轉(zhuǎn),路由1中的狀態(tài)是沒辦法在路由2中獲取到的,當(dāng)然有方法去解決,但是會(huì)帶來更多的代碼和結(jié)構(gòu)上的不合理.

image.png

riverpod提供的是頂層的InheritedWidget來管理所有的狀態(tài),很容易想到ProviderContainer中應(yīng)該是有個(gè)Map來存儲(chǔ)所有的狀態(tài),所有狀態(tài)保存在頂層,下層組件想要使用狀態(tài),通過頂層的InheritedWidget獲取ProviderContainer來使用
riverpod里邊一個(gè)很特別的對象就是ref,在provider(指的riverpod中的狀態(tài))中有ref,在Consumer中也有一個(gè)ref,但是這兩個(gè)ref是不一樣的類型,這里也要說一下riverpod中的provider會(huì)有對應(yīng)的element,所以可以簡單的理解
provider 中的refproviderElement
Consumer 中的refwidgetElement,也就是說context可以被強(qiáng)制轉(zhuǎn)化成ref
關(guān)于providerElement可以類比widget與element的關(guān)系,provider只是一個(gè)配置類,providerElement才是真實(shí)的狀態(tài)管理類,只有providerElement創(chuàng)建才會(huì)產(chǎn)生真正的狀態(tài),所以在Riverpod中經(jīng)常會(huì)看見類似這樣的代碼

final counterStateProvider = StateProvider<int>((ref)=>0)

StateProvider可能會(huì)被提前創(chuàng)建出來,但是真正的狀態(tài)被創(chuàng)建,會(huì)在真正使用的時(shí)候
所以不必?fù)?dān)心全局創(chuàng)建的provider
這里還是要做一個(gè)類比
InheritedElementWidgetElement的關(guān)系與ProviderElementConsumerStatefulElement
InheritedElement中有_dependents,WidgetElement中有_dependencies
ProviderElement中有_dependents,ConsumerStatefulElement中有_dependencies
他們的含義是一樣的_dependents代表哪些組件注冊了刷新回調(diào),_dependencies代表組件在哪些狀態(tài)中注冊了回調(diào),有點(diǎn)繞,但是要理解
上邊說了在頂層有一個(gè)保存所有狀態(tài)的InheritedWidget,但是具體如何操作組件刷新的呢?
類比Provider狀態(tài)管理,Riverpod也提供了兩個(gè)方法readwatch,含義是一樣的read是用來讀數(shù)據(jù),watch不僅用來讀也用來注冊刷新回調(diào)
ConsumerWidget中使用的ref我們上面說到是widgetelement,我們執(zhí)行ref.watch(countProvider)的時(shí)候

@override
Res watch<Res>(ProviderListenable<Res> target) {
  _assertNotDisposed();
  return _dependencies.putIfAbsent(target, () {
    final oldDependency = _oldDependencies?.remove(target);

    if (oldDependency != null) {
      return oldDependency;
    }

    return _container.listen<Res>(
      target,
      (_, __) => markNeedsBuild(),
    );
  }).read() as Res;
}

_container就是頂層的ProviderContainer
_dependencies用來存放_container監(jiān)聽之后的ProviderSubscription,類比StreamSubscription,如果當(dāng)前的element從element tree移除后,可以移除掉在_container中注冊的刷新回調(diào)

@override
ProviderSubscription<State> listen<State>(
  ProviderListenable<State> provider,
  void Function(State? previous, State next) listener, {
  bool fireImmediately = false,
  void Function(Object error, StackTrace stackTrace)? onError,
}) {
  return provider.addListener(
    this,
    listener,
    fireImmediately: fireImmediately,
    onError: onError,
    onDependencyMayHaveChanged: null,
  );
}

最終走到ProvideraddListener 方法 node.readProviderElement(this)本質(zhì)就是找到ProviderElement,這里其實(shí)也發(fā)現(xiàn)了ProviderElementWidgetElement的一一對應(yīng)的關(guān)系是_ProviderStateSubscription來管理的

_ProviderStateSubscription(
  super.source, {
  required this.listenedElement,
  required this.listener,
  required this.onError,
}) {
  final dependents = listenedElement._dependents ??= [];
  dependents.add(this);
}

構(gòu)造函數(shù)可以看到_dependents添加了Subscription,也就是添加了組件的刷新回調(diào),provider中的狀態(tài)變化的時(shí)候可以遍歷_dependents來刷新UI組件,實(shí)際也是這樣做的

void _notifyListeners(
    Result<StateT> newState,
    Result<StateT>? previousStateResult, {
    bool checkUpdateShouldNotify = true,
  }) {
  
  ...
  
    final listeners = _dependents?.toList(growable: false);
    newState.map(
      data: (newState) {
        if (listeners != null) {
          for (var i = 0; i < listeners.length; i++) {
            final listener = listeners[i];
            if (listener is _ProviderStateSubscription) {
              Zone.current.runBinaryGuarded(
                listener.listener,
                previousState,
                newState.state,
              );
            }
          }
        }
      },
      error: (newState) {
        if (listeners != null) {
          for (var i = 0; i < listeners.length; i++) {
            final listener = listeners[i];
            if (listener is _ProviderStateSubscription<StateT>) {
              Zone.current.runBinaryGuarded(
                listener.onError,
                newState.error,
                newState.stackTrace,
              );
            }
          }
        }
      },
    );

    ...
  }

StateProvider舉例,在我們調(diào)用ref.read(countProvider.notifier).state++ 的時(shí)候會(huì)執(zhí)行listenerEntry.listener(value)

set state(T value) {
  assert(_debugIsMounted(), '');
  final previousState = _state;
  _state = value;

  final errors = <Object>[];
  final stackTraces = <StackTrace?>[];
  for (final listenerEntry in _listeners) {
    try {
      listenerEntry.listener(value);
    } catch (error, stackTrace) {
      errors.add(error);
      stackTraces.add(stackTrace);

      if (onError != null) {
        onError!(error, stackTrace);
      } else {
        Zone.current.handleUncaughtError(error, stackTrace);
      }
    }
  }
  if (errors.isNotEmpty) {
    throw StateNotifierListenerError._(errors, stackTraces, this);
  }
}

最終會(huì)調(diào)用到ProviderElementsetState,然后調(diào)用_notifyListeners

void setState(StateT newState) {
  assert(
    () {
      _debugDidSetState = true;
      return true;
    }(),
    '',
  );
  final previousResult = getState();
  final result = _state = ResultData(newState);

  if (_didBuild) {
    _notifyListeners(result, previousResult);
  }
}

setState是在ProviderElement創(chuàng)建的時(shí)候就進(jìn)行了注冊

void create({required bool didChangeDependency}) {
  final provider = this.provider as _StateProviderBase<T>;
  final initialState = provider._create(this);

  final controller = StateController(initialState);
  _controllerNotifier.result = Result.data(controller);

  _removeListener = controller.addListener(
    fireImmediately: true,
    (state) {
      _stateNotifier.result = _controllerNotifier.result;
      setState(state);
    },
  );
}

Riverpod實(shí)際做的對系統(tǒng)InheritedWidget的優(yōu)化,讓狀態(tài)從組件數(shù)中抽離出來,
InheritedWidget時(shí)代,狀態(tài)和組件本身就是一個(gè)東西(InheritedElement就是個(gè)特殊的WidgetElement),自己管理自己的狀態(tài),也就導(dǎo)致他嚴(yán)重依賴組件樹的結(jié)構(gòu),如果兩個(gè)處于不同分支下的狀態(tài)想要相互調(diào)用是不可以的,但是Riverpod對他們進(jìn)行了拆分,狀態(tài)單獨(dú)出來,并且通過索引保存在最頂層,這樣既保持了狀態(tài)和組件的1對1的關(guān)系,又實(shí)現(xiàn)了不同分支下的狀態(tài)之間的相互調(diào)用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锣光,一起剝皮案震驚了整個(gè)濱河市身笤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡大渤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門掸绞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泵三,“玉大人,你說我怎么就攤上這事衔掸√棠唬” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵敞映,是天一觀的道長较曼。 經(jīng)常有香客問我,道長振愿,這世上最難降的妖魔是什么诗芜? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮埃疫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘孩哑。我一直安慰自己栓霜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布横蜒。 她就那樣靜靜地躺著胳蛮,像睡著了一般销凑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仅炊,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天斗幼,我揣著相機(jī)與錄音,去河邊找鬼抚垄。 笑死蜕窿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呆馁。 我是一名探鬼主播桐经,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼浙滤!你這毒婦竟也來了阴挣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤纺腊,失蹤者是張志新(化名)和其女友劉穎畔咧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體揖膜,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡誓沸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了次氨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔽介。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖煮寡,靈堂內(nèi)的尸體忽然破棺而出虹蓄,到底是詐尸還是另有隱情,我是刑警寧澤幸撕,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布薇组,位于F島的核電站,受9級特大地震影響坐儿,放射性物質(zhì)發(fā)生泄漏律胀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一貌矿、第九天 我趴在偏房一處隱蔽的房頂上張望炭菌。 院中可真熱鬧,春花似錦逛漫、人聲如沸黑低。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽克握。三九已至蕾管,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菩暗,已是汗流浹背掰曾。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留停团,地道東北人旷坦。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像客蹋,于是被迫代替她去往敵國和親塞蹭。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容