前言

程式設計技術交流聖地[-Flutter群-] 發起的 狀態管理研究小組,將就 狀態管理 相關話題進行為期 兩個月 的討論。

目前只有內定的 5 個人參與討論,如果你對 狀態管理 有什麼獨特的見解,或想參與其中,可諮詢 張風捷特烈 ,歡迎和我們共同交流。


關於這篇文章的一些內容,我很久之前就想寫的,但一直沒啥源動力,就一直鴿著

這次被捷特大佬催了幾次,終於把這文章寫完了,文章裡有我對狀態管理的一些思考和看法,希望能引起茫茫人海中零星的共鳴。。。

狀態管理的認知

變遷

解耦是眾多思想或框架的基石

就拿最最最經典的MVC來說,統一將模組分為三層

  • Model層:資料管理
  • Controller層:邏輯處理
  • View層:檢視搭建

這個經典的層級劃分能應付很多場景

  • MVP,MVVM也是MVC的變種,本質上都是為了在合適的場景,更合理的解耦

  • 其實這些模式應用在移動端是很合適的,移動端舊時XML的寫法,是獲取其View節點,然後對其節點操作

  • 在JSP的時代,JQuery大行其道,操作DOM節點,重新整理資料;如出一轍。

時代總是在發展中前進,技術也在不停變遷;就像普羅米修斯盜火而來,給世間帶來諸多變化

對View節點操作的思想,固定化的套用在如今的前端是不準確的

如今前端是由眾多"狀態"去控制介面展示的,需要用更加精煉的語言去闡述它

包容萬千

狀態管理的重點也就在其表面:狀態和管理

  • 寥寥四字,就精悍的概括了思想及其靈魂

狀態是頁面的靈魂,是業務邏輯和通用邏輯的錨定符,只要分離出狀態,將其管理,就可以將頁面解耦

一般來說,從狀態管理的概念上,可以解耦出多個層級

極簡模式

這是一種十分簡潔的層級劃分,眾多流行的Flutter狀態管理框架,也是如此劃分的,例如:provider,getx

  • view:介面層
  • Logic:邏輯層 + 狀態層

標準模式

這已經是一種類似MVC的層級劃分了,這種層級也十分常見,例如:cubit(provider和getx也能輕鬆劃分出這種結構)

  • view:介面
  • Logic:邏輯層
  • State:狀態層

嚴格模式

對於標椎模式而言,已經劃分的很到位了,但還有某一類層次沒有劃分出來:使用者和程式互動的行為

說明下:想要劃分出這一層級,代價必然是很大的,會讓框架的使用複雜度進一步上升

  • 後面分析為什麼劃分這一層次,會導致成本很大

常見的狀態管理框架:Bloc,Redux,fish_redux

  • view:介面層
  • Logic:邏輯層
  • State:狀態層
  • Action:行為層

強迫症模式

常見的狀態管理框架:Redux,fish_redux

從圖上來看,這個結構已經有點複雜了,為了解耦資料重新整理這一層次,付出了巨大的成本

  • view:介面層
  • Logic:邏輯層
  • State:狀態層
  • Action:行為層
  • Reducer:這個層級,是專門用於處理資料變化的

思考

對於變化的事物和思想,我們應該去恐懼,去抗拒嗎?

我時常認為:優秀的思想見證變遷,它並不會在時光中衰敗,而是變的越來越璀璨

例如:設計模式

解耦的成本

分離邏輯+狀態層

一個成熟的狀態管理框架,必定將邏輯從介面層裡面劃分處理,這是應該一個狀態管理框架的最樸實的初衷

一些看法

實際上,此時付出的成本是針對框架開發者的,需要開發者去選擇一個合適技術方案,去進行合理的解耦

實現一個狀態管理框架,我此時,或許可以說:

  • 這並不是一件多麼難的事
  • 幾個檔案就能實現一個合理且功能強大的狀態管理框架

此時,螢幕前的你可能會想了:這叼毛可真會吹牛皮,把逗笑了

關於上面的話,我真不是吹牛皮,我看了幾個狀態管理的原始碼後,發現狀態管理的思想其實非常樸實,當然開源框架的程式碼並沒有那麼簡單,基本都做了大量的抽象,方便功能擴充套件,這基本都會對閱讀者產生極大的困擾,尤其是provider,看的頭皮發麻、、、

我將幾個典型的狀態管理的思想提取出來後,用極簡的程式碼復現其執行機制,發現用的都是觀察模式的思想,理解了以後,就並不覺得狀態管理框架多麼的神祕了

我絕沒有任何輕視的思想:他們都是那個莽荒時代裡,偉大的拓荒者!

如何將邏輯+狀態層從介面裡解耦出來?

我總結了幾種很經典的狀態管理的實現機制,因為每一種實現原始碼都有點長,就放在文章後半截了,有興趣的可以看看;每一種實現方式的程式碼都是完整的,可獨立執行的

  • 將邏輯層介面解耦出來

    • 成本在框架端,需要較複雜的實現
    • 一般來說,只解耦倆層,使用上一般較為簡單

  • 解耦狀態層

    • 如果分離出邏輯層,解耦狀態層,一般來說,並不會很難;手動簡單劃分即可,我寫的幾個idea外掛生成模板程式碼,都對該層做了劃分
    • 也可以直接在框架內部直接強行約定,Bloc中的Bloc模式和Cubit模式,redux系列。。。
    • 劃分成本不高,使用成本不高,該層解耦的影響深遠

Action層的成本

Action層是什麼?正如其名字一樣,行為層,使用者和介面上的互動事件都可以劃分到這一層

  • 例如:點選按鈕的事件,輸入事件,上拉下拉事件等等
  • 使用者在介面上生成了這些事件,我們也需要做相應的邏輯去響應

為什麼要劃分Action層?

  • 大家如果寫flutter套娃程式碼寫的很盡興的時候,可能會發現,很多點選事件的互動入口都在widget山裡
  • 互動事件散落在大量的介面程式碼,如果需要跳轉事件調整傳參,找起來會很頭痛
  • 還有一個很重要的方面:實際上互動事件的入口,就是業務入口,需求調整時,找相應業務程式碼也很麻煩!

基於業務會逐漸鬼畜的考量,一些框架劃分出了Action層,統一管理了所有的互動事件

成本

框架側成本

想要統一管理所有的互動事件,實現上難度不是很大

  • 一般情況下,我們可以直接在view層,直接呼叫邏輯層的方法,執行相關有業務邏輯
  • 現在需要將呼叫邏輯層方法的行為,進行統一的管理
  • 所以,需要在呼叫的中間,增加一箇中間層,中轉所有的事件
  • 這個中轉層就是action層,可以管理所有的互動事件

來看下實現思路

框架側實現成本並不高,主要就是對事件的接受和分發

實際上,我們一般也不在乎框架側成本,框架內部實現的再怎麼複雜都無關緊要,用法應該簡潔明瞭

如果內部設計非常精妙,使用起來卻晦澀繁瑣,無疑是給使用者增加心智負擔

使用側成本

劃分出Action層,會給使用者增加一定的使用成本,這是無法避免的

  • 事件定義成本:因為劃分出了事件層,每一種互動,必須在Action層去定義
  • 傳送事件成本:在view層需要將定義的事件用不同的api傳送出去,這個對比以前呼叫區別不大,成本很低
  • 邏輯層處理成本:邏輯層必定會多一個模組或方法,接受分發的方法去分類處理,此處會有一點繁瑣

圖中紅框的模組,是額外的使用成本

外在表現

Bloc不使用Action

  • View層,程式碼簡寫,只是看看其外在表現
  1. class BlBlocCounterPage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return BlocProvider(
  5. create: (BuildContext context) => BlBlocCounterBloc()..init(),
  6. child: Builder(builder: (context) => _buildPage(context)),
  7. );
  8. }
  9. Widget _buildPage(BuildContext context) {
  10. final bloc = BlocProvider.of<BlBlocCounterBloc>(context);
  11. return Scaffold(
  12. ...
  13. floatingActionButton: FloatingActionButton(
  14. //呼叫業務方法
  15. onPressed: () => bloc.increment(),
  16. child: Icon(Icons.add),
  17. ),
  18. );
  19. }
  20. }
  • Bloc層
  1. class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
  2. BlBlocCounterBloc() : super(BlBlocCounterState().init());
  3. void init() async {
  4. ///處理邏輯,呼叫emit方法重新整理
  5. emit(state.clone());
  6. }
  7. }

state層:該演示中,此層不重要,不寫了

Bloc使用Action

  • View層,程式碼簡寫,只是看看其外在表現
  1. class BlBlocCounterPage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return BlocProvider(
  5. create: (BuildContext context) => BlBlocCounterBloc()..add(InitEvent()),
  6. child: Builder(builder: (context) => _buildPage(context)),
  7. );
  8. }
  9. Widget _buildPage(BuildContext context) {
  10. final bloc = BlocProvider.of<BlBlocCounterBloc>(context);
  11. return Scaffold(
  12. ...
  13. floatingActionButton: FloatingActionButton(
  14. onPressed: () => bloc.add(AEvent()),
  15. child: Icon(Icons.add),
  16. ),
  17. );
  18. }
  19. }
  • Bloc層
  1. class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
  2. BlBlocCounterBloc() : super(BlBlocCounterState().init());
  3. @override
  4. Stream<BlBlocCounterState> mapEventToState(BlBlocCounterEvent event) async* {
  5. if (event is InitEvent) {
  6. yield await init();
  7. } else if (event is AEvent) {
  8. yield a();
  9. } else if (event is BEvent) {
  10. yield b();
  11. } else if (event is CEvent) {
  12. yield c();
  13. } else if (event is DEvent) {
  14. yield d();
  15. } else if (event is EEvent) {
  16. yield e();
  17. } else if (event is FEvent) {
  18. yield f();
  19. } else if (event is GEvent) {
  20. yield g();
  21. } else if (event is HEvent) {
  22. yield h();
  23. } else if (event is IEvent) {
  24. yield i();
  25. } else if (event is JEvent) {
  26. yield j();
  27. } else if (event is KEvent) {
  28. yield k();
  29. }
  30. }
  31. ///對應業務方法
  32. ...
  33. }
  • Event層:如果需要傳引數,事件類裡面就需要定義相關變數,實現其建構函式,將view層資料傳輸到bloc層
  1. abstract class BlBlocCounterEvent {}
  2. class InitEvent extends BlBlocCounterEvent {}
  3. class AEvent extends BlBlocCounterEvent {}
  4. class BEvent extends BlBlocCounterEvent {}
  5. class CEvent extends BlBlocCounterEvent {}
  6. .......
  7. class KEvent extends BlBlocCounterEvent {}

state層:該演示中,此層不重要,不寫了

fish_redux的使用表現

  • view
  1. Widget buildView(MainState state, Dispatch dispatch, ViewService viewService) {
  2. return Scaffold(
  3. //頂部AppBar
  4. appBar: mainAppBar(
  5. onTap: () => dispatch(MainActionCreator.toSearch()),
  6. ),
  7. //側邊抽屜模組
  8. drawer: MainDrawer(
  9. data: state,
  10. onTap: (String tag) => dispatch(MainActionCreator.clickDrawer(tag)),
  11. ),
  12. //頁面主體
  13. body: MainBody(
  14. data: state,
  15. onChanged: (int index) => dispatch(MainActionCreator.selectTab(index)),
  16. ),
  17. //底部導航
  18. bottomNavigationBar: MainBottomNavigation(
  19. data: state,
  20. onTap: (int index) => dispatch(MainActionCreator.selectTab(index)),
  21. ),
  22. );
  23. }
  • action層
  1. enum MainAction {
  2. //切換tab
  3. selectTab,
  4. //側邊欄item點選
  5. clickDrawer,
  6. //搜尋
  7. toSearch,
  8. //統一重新整理事件
  9. onRefresh,
  10. }
  11. class MainActionCreator {
  12. static Action toSearch() {
  13. return Action(MainAction.toSearch);
  14. }
  15. static Action selectTab(int index) {
  16. return Action(MainAction.selectTab, payload: index);
  17. }
  18. static Action onRefresh() {
  19. return Action(MainAction.onRefresh);
  20. }
  21. static Action clickDrawer(String tag) {
  22. return Action(MainAction.clickDrawer, payload: tag);
  23. }
  24. }
  • Event
  1. Effect<MainState> buildEffect() {
  2. return combineEffects(<Object, Effect<MainState>>{
  3. //初始化
  4. Lifecycle.initState: _init,
  5. //切換tab
  6. MainAction.selectTab: _selectTab,
  7. //選擇相應抽屜內部的item
  8. MainAction.clickDrawer: _clickDrawer,
  9. //跳轉搜尋頁面
  10. MainAction.toSearch: _toSearch,
  11. });
  12. }
  13. ///眾多業務方法
  14. void _init(Action action, Context<MainState> ctx) async {
  15. ...
  16. }
  • reducer和state層不重要,這地方就不寫了

fish_redux對Action層的劃分以及事件的分發,明顯要比Bloc老道很多

fish_redux使用列舉和一個類就完成了眾多事件的定義;bloc需要繼承類,一個類一個事件

老實說,倆種框架我都用了,bloc這樣寫確實比較麻煩,尤其涉及傳參的時候,就需要在類裡面定義很多變數

總結

上面幾種形式對比,可以發現區別還是蠻大的

增加了Action層,使得使用成本不可避免的飆升

很多人心裡,此時或許都會吐槽:好麻煩,,,

對Action層的思考和演化

通過解耦Action層的設計本質分析,我們會發現一個無法避免的現實!

  • 增加Action層,使用端的成本無法去避免
  • 因為使用端增加的成本,就是框架側的設計核心

當業務逐漸的複雜起來,Action層的劃分是勢在必行的,我們必須歸納事件入口;當業務頻繁調整時,需要快速的去定位對應的業務!

有辦法簡化嗎?

Action層的劃分,會一定程度上增加使用者的負擔,有什麼辦法可以簡化?同時又能達到管理事件入口的效果?

我曾對View層瘋狂的套娃,做了很多思考,關於一些拆分形式做了一些嘗試

拆分後的效果,將View層和Action很好的結合起來了,具體操作:Flutter 改善套娃地獄問題(仿喜馬拉雅PC頁面舉例)

  • 看下拆分後的程式碼效果

    • 因為將View分模組劃分清晰了,對外暴露方法就是業務事件,可以很輕鬆的定位到對應的業務了
    • 如此形式劃分後,對應的頁面結構也變得異常清晰,修改頁面對應的模組也很輕鬆了

  • 對View層進行相關改造後

    • 可以非常方便的定位業務和介面模組
    • 同時也避免的Action層一系列稍顯繁瑣的操作

總結

框架的約定,可以規範眾多行為習慣不同的開發者

後來我提出的對View層的拆分,只能依靠開發者本身的意識

這裡,我給出一種不一樣的方式,其中的取捨,只能由各位自己決定嘍

我目前一直都是使用View層的拆分,自我感覺對後期複雜模組的維護,非常友好~~

Reducer層的吐槽

可能是我太菜了,一直感受不到這一層分化的妙處

我用fish_redux也寫了很多頁面(用了一年了),之前也會將相關資料通過Action層傳遞到Reducer,然後進行相應的重新整理,這導致了一個問題!

  • 我重新整理個數據,就需要建立一個Action
  • 然後在Reducer解析傳過來來的,往clone方法裡賦值,導致我想修改資料的時候,必須先要去Effect層去看邏輯,然後去Reducer裡面修改賦值
  • 來回跳,麻煩到爆!

被繞了多次,煩躁了多次後,我直接把Reducer層寫成了一個重新整理方法!

  1. Reducer<WebViewState> buildReducer() {
  2. return asReducer(
  3. <Object, Reducer<WebViewState>>{
  4. WebViewAction.onRefresh: _onRefresh,
  5. },
  6. );
  7. }
  8. WebViewState _onRefresh(WebViewState state, Action action) {
  9. return state.clone();
  10. }

就算在複雜的模組,我也沒感受到他給我帶來的好處,我就只能把他無限弱化成一個重新整理方法了

狀態管理的幾種實現

這是我看了一些狀態管理的原始碼

  • 總結出的幾種狀態管理的重新整理機制
  • 任選一種,都可以搓出你自己的狀態管理框架

之前的幾篇原始碼剖析文章寫過,整理了下,做個總結

爛大街的實現

實現難度最小

這是一種非常常見的實現

  • 這是一種簡單,易用,強大的實現
  • 同時由於難度不高,也是一種爛大街的實現

實現

需要實現一個管理邏輯層例項的的中介軟體:依賴注入的實現

也可以使用InheritedWidget儲存和傳遞邏輯層例項(Bloc就是這樣做的);但是自己管理,可以大大拓寬使用場景,此處就自己實現一個管理例項的中介軟體

  • 這邊只實現三個基礎api
  1. ///依賴注入,外部可將例項,注入該類中,由該類管理
  2. class Easy {
  3. ///注入例項
  4. static T put<T>(T dependency, {String? tag}) =>
  5. _EasyInstance().put(dependency, tag: tag);
  6. ///獲取注入的例項
  7. static T find<T>({String? tag, String? key}) =>
  8. _EasyInstance().find<T>(tag: tag, key: key);
  9. ///刪除例項
  10. static bool delete<T>({String? tag, String? key}) =>
  11. _EasyInstance().delete<T>(tag: tag, key: key);
  12. }
  13. ///具體邏輯
  14. class _EasyInstance {
  15. factory _EasyInstance() => _instance ??= _EasyInstance._();
  16. static _EasyInstance? _instance;
  17. _EasyInstance._();
  18. static final Map<String, _InstanceInfo> _single = {};
  19. ///注入例項
  20. T put<T>(T dependency, {String? tag}) {
  21. final key = _getKey(T, tag);
  22. //只儲存第一次注入:針對自動重新整理機制優化,每次熱過載的時候,資料不會重置
  23. _single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
  24. return find<T>(tag: tag);
  25. }
  26. ///獲取注入的例項
  27. T find<T>({String? tag, String? key}) {
  28. final newKey = key ?? _getKey(T, tag);
  29. var info = _single[newKey];
  30. if (info?.value != null) {
  31. return info!.value;
  32. } else {
  33. throw '"$T" not found. You need to call "Easy.put($T())""';
  34. }
  35. }
  36. ///刪除例項
  37. bool delete<T>({String? tag, String? key}) {
  38. final newKey = key ?? _getKey(T, tag);
  39. if (!_single.containsKey(newKey)) {
  40. print('Instance "$newKey" already removed.');
  41. return false;
  42. }
  43. _single.remove(newKey);
  44. print('Instance "$newKey" deleted.');
  45. return true;
  46. }
  47. String _getKey(Type type, String? name) {
  48. return name == null ? type.toString() : type.toString() + name;
  49. }
  50. }
  51. class _InstanceInfo<T> {
  52. _InstanceInfo(this.value);
  53. T value;
  54. }

定義一個監聽和基類

  • 也可以使用ChangeNotifier;此處我們自己簡單定義個
  1. class EasyXNotifier {
  2. List<VoidCallback> _listeners = [];
  3. void addListener(VoidCallback listener) => _listeners.add(listener);
  4. void removeListener(VoidCallback listener) {
  5. for (final entry in _listeners) {
  6. if (entry == listener) {
  7. _listeners.remove(entry);
  8. return;
  9. }
  10. }
  11. }
  12. void dispose() => _listeners.clear();
  13. void notify() {
  14. if (_listeners.isEmpty) return;
  15. for (final entry in _listeners) {
  16. entry.call();
  17. }
  18. }
  19. }
  • 我這地方寫的極簡,相關生命週期都沒加,為了程式碼簡潔,這個暫且不表
  1. class EasyXController {
  2. EasyXNotifier xNotifier = EasyXNotifier();
  3. ///重新整理控制元件
  4. void update() => xNotifier.notify();
  5. }

再來看看最核心的EasyBuilder控制元件:這就搞定了!

  • 實現程式碼寫的極其簡單,希望大家思路能有所明晰
  1. ///重新整理控制元件,自帶回收機制
  2. class EasyBuilder<T extends EasyXController> extends StatefulWidget {
  3. final Widget Function(T logic) builder;
  4. final String? tag;
  5. final bool autoRemove;
  6. const EasyBuilder({
  7. Key? key,
  8. required this.builder,
  9. this.autoRemove = true,
  10. this.tag,
  11. }) : super(key: key);
  12. @override
  13. _EasyBuilderState<T> createState() => _EasyBuilderState<T>();
  14. }
  15. class _EasyBuilderState<T extends EasyXController> extends State<EasyBuilder<T>> {
  16. late T controller;
  17. @override
  18. void initState() {
  19. super.initState();
  20. ///此處是整個類的靈魂程式碼
  21. controller = Easy.find<T>(tag: widget.tag);
  22. controller.xNotifier.addListener(() {
  23. if (mounted) setState(() {});
  24. });
  25. }
  26. @override
  27. void dispose() {
  28. if (widget.autoRemove) {
  29. Easy.delete<T>(tag: widget.tag);
  30. }
  31. controller.xNotifier.dispose();
  32. super.dispose();
  33. }
  34. @override
  35. Widget build(BuildContext context) => widget.builder(controller);
  36. }

使用

  • 使用很簡單,先看下邏輯層
  1. class EasyXCounterLogic extends EasyXController {
  2. var count = 0;
  3. void increase() {
  4. ++count;
  5. update();
  6. }
  7. }
  • 介面層
  1. class EasyXCounterPage extends StatelessWidget {
  2. final logic = Easy.put(EasyXCounterLogic());
  3. @override
  4. Widget build(BuildContext context) {
  5. return BaseScaffold(
  6. appBar: AppBar(title: const Text('EasyX-自定義EasyBuilder重新整理機制')),
  7. body: Center(
  8. child: EasyBuilder<EasyXCounterLogic>(builder: (logic) {
  9. return Text(
  10. '點選了 ${logic.count} 次',
  11. style: TextStyle(fontSize: 30.0),
  12. );
  13. }),
  14. ),
  15. floatingActionButton: FloatingActionButton(
  16. onPressed: () => logic.increase(),
  17. child: Icon(Icons.add),
  18. ),
  19. );
  20. }
  21. }
  • 效果圖

InheritedWidget的實現

實現具有一定的難度

更加詳細的解析可檢視:Flutter Provider的另一面

先來看下InheritedWidget它自帶一些功能

  • 儲存資料,且資料可以隨著父子節點傳遞
  • 自帶區域性重新整理機制

資料傳遞

區域性重新整理

InheritedWidget對子節點的Element,有個強大的操作功能

  • 可以將子widget的element例項,儲存在自身的InheritedElement中的_dependents變數中
  • 呼叫其notifyClients方法,會遍歷_dependents中的子Element,然後呼叫子Element的markNeedsBuild方法,就完成了定點重新整理子節點的操作

有了上面這倆個關鍵知識,就可以輕鬆的實現一個強大的狀態管理框架了,來看下實現

實現

  • ChangeNotifierEasyP:類比Provider的ChangeNotifierProvider
  1. class ChangeNotifierEasyP<T extends ChangeNotifier> extends StatelessWidget {
  2. ChangeNotifierEasyP({
  3. Key? key,
  4. required this.create,
  5. this.builder,
  6. this.child,
  7. }) : super(key: key);
  8. final T Function(BuildContext context) create;
  9. final Widget Function(BuildContext context)? builder;
  10. final Widget? child;
  11. @override
  12. Widget build(BuildContext context) {
  13. assert(
  14. builder != null || child != null,
  15. '$runtimeType must specify a child',
  16. );
  17. return EasyPInherited(
  18. create: create,
  19. child: builder != null
  20. ? Builder(builder: (context) => builder!(context))
  21. : child!,
  22. );
  23. }
  24. }
  25. class EasyPInherited<T extends ChangeNotifier> extends InheritedWidget {
  26. EasyPInherited({
  27. Key? key,
  28. required Widget child,
  29. required this.create,
  30. }) : super(key: key, child: child);
  31. final T Function(BuildContext context) create;
  32. @override
  33. bool updateShouldNotify(InheritedWidget oldWidget) => false;
  34. @override
  35. InheritedElement createElement() => EasyPInheritedElement(this);
  36. }
  37. class EasyPInheritedElement<T extends ChangeNotifier> extends InheritedElement {
  38. EasyPInheritedElement(EasyPInherited<T> widget) : super(widget);
  39. bool _firstBuild = true;
  40. bool _shouldNotify = false;
  41. late T _value;
  42. late void Function() _callBack;
  43. T get value => _value;
  44. @override
  45. void performRebuild() {
  46. if (_firstBuild) {
  47. _firstBuild = false;
  48. _value = (widget as EasyPInherited<T>).create(this);
  49. _value.addListener(_callBack = () {
  50. // 處理重新整理邏輯,此處無法直接呼叫notifyClients
  51. // 會導致owner!._debugCurrentBuildTarget為null,觸發斷言條件,無法向後執行
  52. _shouldNotify = true;
  53. markNeedsBuild();
  54. });
  55. }
  56. super.performRebuild();
  57. }
  58. @override
  59. Widget build() {
  60. if (_shouldNotify) {
  61. _shouldNotify = false;
  62. notifyClients(widget);
  63. }
  64. return super.build();
  65. }
  66. @override
  67. void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  68. //此處就直接重新整理新增的監聽子Element了,不各種super了
  69. dependent.markNeedsBuild();
  70. // super.notifyDependent(oldWidget, dependent);
  71. }
  72. @override
  73. void unmount() {
  74. _value.removeListener(_callBack);
  75. _value.dispose();
  76. super.unmount();
  77. }
  78. }
  • EasyP:類比Provider的Provider類
  1. class EasyP {
  2. /// 獲取EasyP例項
  3. /// 獲取例項的時候,listener引數老是寫錯,這邊直接用倆個方法區分了
  4. static T of<T extends ChangeNotifier>(BuildContext context) {
  5. return _getInheritedElement<T>(context).value;
  6. }
  7. /// 註冊監聽控制元件
  8. static T register<T extends ChangeNotifier>(BuildContext context) {
  9. var element = _getInheritedElement<T>(context);
  10. context.dependOnInheritedElement(element);
  11. return element.value;
  12. }
  13. /// 獲取距離當前Element最近繼承InheritedElement<T>的元件
  14. static EasyPInheritedElement<T>
  15. _getInheritedElement<T extends ChangeNotifier>(BuildContext context) {
  16. var inheritedElement = context
  17. .getElementForInheritedWidgetOfExactType<EasyPInherited<T>>()
  18. as EasyPInheritedElement<T>?;
  19. if (inheritedElement == null) {
  20. throw EasyPNotFoundException(T);
  21. }
  22. return inheritedElement;
  23. }
  24. }
  25. class EasyPNotFoundException implements Exception {
  26. EasyPNotFoundException(this.valueType);
  27. final Type valueType;
  28. @override
  29. String toString() => 'Error: Could not find the EasyP<$valueType>';
  30. }
  • build:最後整一個Build類就行了
  1. class EasyPBuilder<T extends ChangeNotifier> extends StatelessWidget {
  2. const EasyPBuilder(
  3. this.builder, {
  4. Key? key,
  5. }) : super(key: key);
  6. final Widget Function() builder;
  7. @override
  8. Widget build(BuildContext context) {
  9. EasyP.register<T>(context);
  10. return builder();
  11. }
  12. }

大功告成,上面這三個類,就基於InheritedWidget自帶的功能,實現了一套狀態管理框架

  • 實現了局部重新整理功能
  • 實現了邏輯層例項,可以隨著Widget父子節點傳遞功能

使用

用法基本和Provider一摸一樣...

  • view
  1. class CounterEasyPPage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return ChangeNotifierEasyP(
  5. create: (BuildContext context) => CounterEasyP(),
  6. builder: (context) => _buildPage(context),
  7. );
  8. }
  9. Widget _buildPage(BuildContext context) {
  10. final easyP = EasyP.of<CounterEasyP>(context);
  11. return Scaffold(
  12. appBar: AppBar(title: Text('自定義狀態管理框架-EasyP範例')),
  13. body: Center(
  14. child: EasyPBuilder<CounterEasyP>(() {
  15. return Text(
  16. '點選了 ${easyP.count} 次',
  17. style: TextStyle(fontSize: 30.0),
  18. );
  19. }),
  20. ),
  21. floatingActionButton: FloatingActionButton(
  22. onPressed: () => easyP.increment(),
  23. child: Icon(Icons.add),
  24. ),
  25. );
  26. }
  27. }
  • easyP
  1. class CounterEasyP extends ChangeNotifier {
  2. int count = 0;
  3. void increment() {
  4. count++;
  5. notifyListeners();
  6. }
  7. }
  • 效果圖:

自動化重新整理的實現

實現需要一些的靈感

自動化重新整理的實現

  • 將單個狀態變數和重新整理元件,建立起了連線
  • 一但變數數值改變,重新整理元件自動重新整理
  • 某狀態變化,只會自動觸發其重新整理元件,其它重新整理元件並不觸發

實現

同樣的,需要管理其邏輯類的中介軟體;為了範例完整,再寫下這個依賴管理類

  1. ///依賴注入,外部可將例項,注入該類中,由該類管理
  2. class Easy {
  3. ///注入例項
  4. static T put<T>(T dependency, {String? tag}) =>
  5. _EasyInstance().put(dependency, tag: tag);
  6. ///獲取注入的例項
  7. static T find<T>({String? tag, String? key}) =>
  8. _EasyInstance().find<T>(tag: tag, key: key);
  9. ///刪除例項
  10. static bool delete<T>({String? tag, String? key}) =>
  11. _EasyInstance().delete<T>(tag: tag, key: key);
  12. }
  13. ///具體邏輯
  14. class _EasyInstance {
  15. factory _EasyInstance() => _instance ??= _EasyInstance._();
  16. static _EasyInstance? _instance;
  17. _EasyInstance._();
  18. static final Map<String, _InstanceInfo> _single = {};
  19. ///注入例項
  20. T put<T>(T dependency, {String? tag}) {
  21. final key = _getKey(T, tag);
  22. //只儲存第一次注入:針對自動重新整理機制優化,每次熱過載的時候,資料不會重置
  23. _single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
  24. return find<T>(tag: tag);
  25. }
  26. ///獲取注入的例項
  27. T find<T>({String? tag, String? key}) {
  28. final newKey = key ?? _getKey(T, tag);
  29. var info = _single[newKey];
  30. if (info?.value != null) {
  31. return info!.value;
  32. } else {
  33. throw '"$T" not found. You need to call "Easy.put($T())""';
  34. }
  35. }
  36. ///刪除例項
  37. bool delete<T>({String? tag, String? key}) {
  38. final newKey = key ?? _getKey(T, tag);
  39. if (!_single.containsKey(newKey)) {
  40. print('Instance "$newKey" already removed.');
  41. return false;
  42. }
  43. _single.remove(newKey);
  44. print('Instance "$newKey" deleted.');
  45. return true;
  46. }
  47. String _getKey(Type type, String? name) {
  48. return name == null ? type.toString() : type.toString() + name;
  49. }
  50. }
  51. class _InstanceInfo<T> {
  52. _InstanceInfo(this.value);
  53. T value;
  54. }
  • 自定義一個監聽類
  1. class EasyXNotifier {
  2. List<VoidCallback> _listeners = [];
  3. void addListener(VoidCallback listener) => _listeners.add(listener);
  4. void removeListener(VoidCallback listener) {
  5. for (final entry in _listeners) {
  6. if (entry == listener) {
  7. _listeners.remove(entry);
  8. return;
  9. }
  10. }
  11. }
  12. void dispose() => _listeners.clear();
  13. void notify() {
  14. if (_listeners.isEmpty) return;
  15. for (final entry in _listeners) {
  16. entry.call();
  17. }
  18. }
  19. }

在自動重新整理的機制中,需要將基礎型別進行封裝

  • 主要邏輯在Rx中
  • set value 和 get value是關鍵
  1. ///拓展函式
  2. extension IntExtension on int {
  3. RxInt get ebs => RxInt(this);
  4. }
  5. extension StringExtension on String {
  6. RxString get ebs => RxString(this);
  7. }
  8. extension DoubleExtension on double {
  9. RxDouble get ebs => RxDouble(this);
  10. }
  11. extension BoolExtension on bool {
  12. RxBool get ebs => RxBool(this);
  13. }
  14. ///封裝各型別
  15. class RxInt extends Rx<int> {
  16. RxInt(int initial) : super(initial);
  17. RxInt operator +(int other) {
  18. value = value + other;
  19. return this;
  20. }
  21. RxInt operator -(int other) {
  22. value = value - other;
  23. return this;
  24. }
  25. }
  26. class RxDouble extends Rx<double> {
  27. RxDouble(double initial) : super(initial);
  28. RxDouble operator +(double other) {
  29. value = value + other;
  30. return this;
  31. }
  32. RxDouble operator -(double other) {
  33. value = value - other;
  34. return this;
  35. }
  36. }
  37. class RxString extends Rx<String> {
  38. RxString(String initial) : super(initial);
  39. }
  40. class RxBool extends Rx<bool> {
  41. RxBool(bool initial) : super(initial);
  42. }
  43. ///主體邏輯
  44. class Rx<T> {
  45. EasyXNotifier subject = EasyXNotifier();
  46. Rx(T initial) {
  47. _value = initial;
  48. }
  49. late T _value;
  50. bool firstRebuild = true;
  51. String get string => value.toString();
  52. @override
  53. String toString() => value.toString();
  54. set value(T val) {
  55. if (_value == val && !firstRebuild) return;
  56. firstRebuild = false;
  57. _value = val;
  58. subject.notify();
  59. }
  60. T get value {
  61. if (RxEasy.proxy != null) {
  62. RxEasy.proxy!.addListener(subject);
  63. }
  64. return _value;
  65. }
  66. }

需要寫一個非常重要的中轉類,這個也會儲存響應式變數的監聽物件

  • 這個類有著非常核心的邏輯:他將響應式變數和重新整理控制元件關聯起來了!
  1. class RxEasy {
  2. EasyXNotifier easyXNotifier = EasyXNotifier();
  3. Map<EasyXNotifier, String> _listenerMap = {};
  4. bool get canUpdate => _listenerMap.isNotEmpty;
  5. static RxEasy? proxy;
  6. void addListener(EasyXNotifier notifier) {
  7. if (!_listenerMap.containsKey(notifier)) {
  8. //變數監聽中重新整理
  9. notifier.addListener(() {
  10. //重新整理ebx中新增的監聽
  11. easyXNotifier.notify();
  12. });
  13. //新增進入map中
  14. _listenerMap[notifier] = '';
  15. }
  16. }
  17. }

重新整理控制元件Ebx

  1. typedef WidgetCallback = Widget Function();
  2. class Ebx extends StatefulWidget {
  3. const Ebx(this.builder, {Key? key}) : super(key: key);
  4. final WidgetCallback builder;
  5. @override
  6. _EbxState createState() => _EbxState();
  7. }
  8. class _EbxState extends State<Ebx> {
  9. RxEasy _rxEasy = RxEasy();
  10. @override
  11. void initState() {
  12. super.initState();
  13. _rxEasy.easyXNotifier.addListener(() {
  14. if (mounted) setState(() {});
  15. });
  16. }
  17. Widget get notifyChild {
  18. final observer = RxEasy.proxy;
  19. RxEasy.proxy = _rxEasy;
  20. final result = widget.builder();
  21. if (!_rxEasy.canUpdate) {
  22. throw 'Widget lacks Rx type variables';
  23. }
  24. RxEasy.proxy = observer;
  25. return result;
  26. }
  27. @override
  28. Widget build(BuildContext context) {
  29. return notifyChild;
  30. }
  31. @override
  32. void dispose() {
  33. _rxEasy.easyXNotifier.dispose();
  34. super.dispose();
  35. }
  36. }

在自動重新整理機制中,回收依賴例項需要針對處理

此處我寫了一個回收控制元件,可以完成例項的自動回收

  • 命名的含義,將例項和控制元件繫結,控制元件被回收時,邏輯層例項也將被自動回收
  1. class EasyBindWidget extends StatefulWidget {
  2. const EasyBindWidget({
  3. Key? key,
  4. this.bind,
  5. this.tag,
  6. this.binds,
  7. this.tags,
  8. required this.child,
  9. }) : assert(
  10. binds == null || tags == null || binds.length == tags.length,
  11. 'The binds and tags arrays length should be equal\n'
  12. 'and the elements in the two arrays correspond one-to-one',
  13. ),
  14. super(key: key);
  15. final Object? bind;
  16. final String? tag;
  17. final List<Object>? binds;
  18. final List<String>? tags;
  19. final Widget child;
  20. @override
  21. _EasyBindWidgetState createState() => _EasyBindWidgetState();
  22. }
  23. class _EasyBindWidgetState extends State<EasyBindWidget> {
  24. @override
  25. Widget build(BuildContext context) {
  26. return widget.child;
  27. }
  28. @override
  29. void dispose() {
  30. _closeController();
  31. _closeControllers();
  32. super.dispose();
  33. }
  34. void _closeController() {
  35. if (widget.bind == null) {
  36. return;
  37. }
  38. var key = widget.bind.runtimeType.toString() + (widget.tag ?? '');
  39. Easy.delete(key: key);
  40. }
  41. void _closeControllers() {
  42. if (widget.binds == null) {
  43. return;
  44. }
  45. for (var i = 0; i < widget.binds!.length; i++) {
  46. var type = widget.binds![i].runtimeType.toString();
  47. if (widget.tags == null) {
  48. Easy.delete(key: type);
  49. } else {
  50. var key = type + (widget.tags?[i] ?? '');
  51. Easy.delete(key: key);
  52. }
  53. }
  54. }
  55. }

使用

  • 邏輯層
  1. class EasyXEbxCounterLogic {
  2. RxInt count = 0.ebs;
  3. ///自增
  4. void increase() => ++count;
  5. }
  • 介面層:頁面頂節點套了一個EasyBindWidget,可以保證依賴注入例項可以自動回收
  1. class EasyXEbxCounterPage extends StatelessWidget {
  2. final logic = Easy.put(EasyXEbxCounterLogic());
  3. @override
  4. Widget build(BuildContext context) {
  5. return EasyBindWidget(
  6. bind: logic,
  7. child: BaseScaffold(
  8. appBar: AppBar(title: const Text('EasyX-自定義Ebx重新整理機制')),
  9. body: Center(
  10. child: Ebx(() {
  11. return Text(
  12. '點選了 ${logic.count.value} 次',
  13. style: TextStyle(fontSize: 30.0),
  14. );
  15. }),
  16. ),
  17. floatingActionButton: FloatingActionButton(
  18. onPressed: () => logic.increase(),
  19. child: Icon(Icons.add),
  20. ),
  21. ),
  22. );
  23. }
  24. }
  • 效果圖

最後

本文總體上,對狀態管理的各個層次劃分做了一些思考和一點個人的見解,文章後半截也給出了一些狀態管理的實現方案

文章裡的內容對想設計狀態管理的靚仔,應該有一些幫助;如果你有相關不同的意見,歡迎在評論區討論

相關地址