前言
由 編程技術交流圣地[-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層,代碼簡寫,只是看看其外在表現
class BlBlocCounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => BlBlocCounterBloc()..init(),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final bloc = BlocProvider.of<BlBlocCounterBloc>(context);
return Scaffold(
...
floatingActionButton: FloatingActionButton(
//呼叫業務方法
onPressed: () => bloc.increment(),
child: Icon(Icons.add),
),
);
}
}
- Bloc層
class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
BlBlocCounterBloc() : super(BlBlocCounterState().init());
void init() async {
///處理邏輯,呼叫emit方法重繪
emit(state.clone());
}
...
}
state層:該演示中,此層不重要,不寫了
Bloc使用Action
- View層,代碼簡寫,只是看看其外在表現
class BlBlocCounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => BlBlocCounterBloc()..add(InitEvent()),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final bloc = BlocProvider.of<BlBlocCounterBloc>(context);
return Scaffold(
...
floatingActionButton: FloatingActionButton(
onPressed: () => bloc.add(AEvent()),
child: Icon(Icons.add),
),
);
}
}
- Bloc層
class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
BlBlocCounterBloc() : super(BlBlocCounterState().init());
@override
Stream<BlBlocCounterState> mapEventToState(BlBlocCounterEvent event) async* {
if (event is InitEvent) {
yield await init();
} else if (event is AEvent) {
yield a();
} else if (event is BEvent) {
yield b();
} else if (event is CEvent) {
yield c();
} else if (event is DEvent) {
yield d();
} else if (event is EEvent) {
yield e();
} else if (event is FEvent) {
yield f();
} else if (event is GEvent) {
yield g();
} else if (event is HEvent) {
yield h();
} else if (event is IEvent) {
yield i();
} else if (event is JEvent) {
yield j();
} else if (event is KEvent) {
yield k();
}
}
///對應業務方法
...
}
- Event層:如果需要傳引數,事件類里面就需要定義相關變數,實作其建構式,將view層資料傳輸到bloc層
abstract class BlBlocCounterEvent {}
class InitEvent extends BlBlocCounterEvent {}
class AEvent extends BlBlocCounterEvent {}
class BEvent extends BlBlocCounterEvent {}
class CEvent extends BlBlocCounterEvent {}
.......
class KEvent extends BlBlocCounterEvent {}
state層:該演示中,此層不重要,不寫了
fish_redux的使用表現
- view
Widget buildView(MainState state, Dispatch dispatch, ViewService viewService) {
return Scaffold(
//頂部AppBar
appBar: mainAppBar(
onTap: () => dispatch(MainActionCreator.toSearch()),
),
//側邊抽屜模塊
drawer: MainDrawer(
data: state,
onTap: (String tag) => dispatch(MainActionCreator.clickDrawer(tag)),
),
//頁面主體
body: MainBody(
data: state,
onChanged: (int index) => dispatch(MainActionCreator.selectTab(index)),
),
//底部導航
bottomNavigationBar: MainBottomNavigation(
data: state,
onTap: (int index) => dispatch(MainActionCreator.selectTab(index)),
),
);
}
- action層
enum MainAction {
//切換tab
selectTab,
//側邊欄item點擊
clickDrawer,
//搜索
toSearch,
//統一重繪事件
onRefresh,
}
class MainActionCreator {
static Action toSearch() {
return Action(MainAction.toSearch);
}
static Action selectTab(int index) {
return Action(MainAction.selectTab, payload: index);
}
static Action onRefresh() {
return Action(MainAction.onRefresh);
}
static Action clickDrawer(String tag) {
return Action(MainAction.clickDrawer, payload: tag);
}
}
- Effect
Effect<MainState> buildEffect() {
return combineEffects(<Object, Effect<MainState>>{
//初始化
Lifecycle.initState: _init,
//切換tab
MainAction.selectTab: _selectTab,
//選擇相應抽屜內部的item
MainAction.clickDrawer: _clickDrawer,
//跳轉搜索頁面
MainAction.toSearch: _toSearch,
});
}
///眾多業務方法
void _init(Action action, Context<MainState> ctx) async {
...
}
- reducer和state層不重要,這地方就不寫了
fish_redux對Action層的劃分以及事件的分發,明顯要比Bloc老道很多
fish_redux使用列舉和一個類就完成了眾多事件的定義;bloc需要繼承類,一個類一個事件
老實說,倆種框架我都用了,bloc這樣寫確實比較麻煩,尤其涉及傳參的時候,就需要在類里面定義很多變數
總結
上面幾種形式對比,可以發現區別還是蠻大的
增加了Action層,使得使用成本不可避免的飆升
很多人心里,此時或許都會吐槽:好麻煩,,,

對Action層的思考和演化
通過對分離Action層的設計本質分析,我們會發現一個無法避免的現實!
- 增加Action層,使用端的成本無法避免
- 因為使用端增加的成本,就是框架側的設計核心

當業務逐漸的復雜起來,Action層的劃分是勢在必行的,我們必須歸納事件入口;當業務頻繁調整時,需要能快速的定位對應的業務!
有辦法簡化嗎?
Action層的劃分,會一定程度上增加使用者的負擔,有什么辦法可以簡化呢?同時又能達到管理事件入口的效果?
我曾對View層瘋狂套娃的Widget,做了很多思考,對拆分形式做了一些嘗試
拆分后的效果,將View層和Action很好的結合起來了,具體操作:Flutter 改善套娃地獄問題(仿喜馬拉雅PC頁面舉例)
- 看下拆分后的代碼效果
- 因為將View分模塊劃分清晰了,對外暴露方法就是業務事件,可以很輕松的定位到對應的業務了
- 如此形式劃分后,對應的頁面結構也變得例外清晰,修改頁面對應的模塊也很輕松了

- 對View層進行相關改造后
- 可以非常方便的定位業務和界面模塊
- 同時也避免的Action層一系列稍顯繁瑣的操作

總結
框架的約定,可以規范眾多行為習慣不同的開發者
后面我提出的對View層的拆分,只能依靠開發者本身的意識
這里,我給出一種不一樣的方式,其中的取舍,只能由各位自己決定嘍
我目前一直都是使用View層的拆分,自我感覺對后期復雜模塊的維護,非常友好~~
Reducer層的吐槽
可能是我太菜了,一直感受不到這一層分化的妙處
我用fish_redux也寫了很多頁面(用了一年了),之前也會將相關資料通過Action層傳遞到Reducer,然后進行相應的重繪,這導致了一個問題!
- 我每次重繪不同行為的資料,就需要創建一個Action
- 然后在Reducer層決議傳過來的資料,再往clone物件里賦值,導致我想修改資料的時候,必須先要去Effect層去看邏輯,然后去Reducer里面修改賦值
- 來回跳,麻煩到爆!
被繞了多次,煩躁了多次后,我直接把Reducer層寫成了一個重繪方法!
Reducer<WebViewState> buildReducer() {
return asReducer(
<Object, Reducer<WebViewState>>{
WebViewAction.onRefresh: _onRefresh,
},
);
}
WebViewState _onRefresh(WebViewState state, Action action) {
return state.clone();
}
就算在復雜的模塊,我也沒感受到他給我帶來的好處,我就只能把他無限榷訓成一個重繪方法了

狀態管理的幾種實作
這是我看了一些狀態管理的原始碼
- 總結出的幾種狀態管理的重繪機制
- 任選一種,都可以搓出你自己的狀態管理框架
之前的幾篇原始碼剖析文章寫過,整理了下,做個總結

爛大街的實作
實作難度最小 ?
這是一種非常常見的實作
- 這是一種簡單,易用,強大的實作
- 同時由于難度不高,也是一種爛大街的實作
實作
需要實作一個管理邏輯層實體的的中間件:依賴注入的實作
也可以使用InheritedWidget保存和傳遞邏輯層實體(Bloc就是這樣做的);但是自己管理,可以大大拓寬使用場景,此處就自己實作一個管理實體的中間件
- 這邊只實作三個基礎api
///依賴注入,外部可將實體,注入該類中,由該類管理
class Easy {
///注入實體
static T put<T>(T dependency, {String? tag}) =>
_EasyInstance().put(dependency, tag: tag);
///獲取注入的實體
static T find<T>({String? tag, String? key}) =>
_EasyInstance().find<T>(tag: tag, key: key);
///洗掉實體
static bool delete<T>({String? tag, String? key}) =>
_EasyInstance().delete<T>(tag: tag, key: key);
}
///具體邏輯
class _EasyInstance {
factory _EasyInstance() => _instance ??= _EasyInstance._();
static _EasyInstance? _instance;
_EasyInstance._();
static final Map<String, _InstanceInfo> _single = {};
///注入實體
T put<T>(T dependency, {String? tag}) {
final key = _getKey(T, tag);
//只保存第一次注入:針對自動重繪機制優化,每次熱多載的時候,資料不會重置
_single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
return find<T>(tag: tag);
}
///獲取注入的實體
T find<T>({String? tag, String? key}) {
final newKey = key ?? _getKey(T, tag);
var info = _single[newKey];
if (info?.value != null) {
return info!.value;
} else {
throw '"$T" not found. You need to call "Easy.put($T())""';
}
}
///洗掉實體
bool delete<T>({String? tag, String? key}) {
final newKey = key ?? _getKey(T, tag);
if (!_single.containsKey(newKey)) {
print('Instance "$newKey" already removed.');
return false;
}
_single.remove(newKey);
print('Instance "$newKey" deleted.');
return true;
}
String _getKey(Type type, String? name) {
return name == null ? type.toString() : type.toString() + name;
}
}
class _InstanceInfo<T> {
_InstanceInfo(this.value);
T value;
}
定義一個監聽和基類
- 也可以使用ChangeNotifier;此處我們自己簡單定義個
class EasyXNotifier {
List<VoidCallback> _listeners = [];
void addListener(VoidCallback listener) => _listeners.add(listener);
void removeListener(VoidCallback listener) {
for (final entry in _listeners) {
if (entry == listener) {
_listeners.remove(entry);
return;
}
}
}
void dispose() => _listeners.clear();
void notify() {
if (_listeners.isEmpty) return;
for (final entry in _listeners) {
entry.call();
}
}
}
- 我這地方寫的極簡,相關生命周期都沒加,為了代碼簡潔,這個暫且不表
class EasyXController {
EasyXNotifier xNotifier = EasyXNotifier();
///重繪控制元件
void update() => xNotifier.notify();
}
再來看看最核心的EasyBuilder控制元件:這就搞定了!
- 實作代碼寫的極其簡單,希望大家思路能有所明晰
///重繪控制元件,自帶回識訓制
class EasyBuilder<T extends EasyXController> extends StatefulWidget {
final Widget Function(T logic) builder;
final String? tag;
final bool autoRemove;
const EasyBuilder({
Key? key,
required this.builder,
this.autoRemove = true,
this.tag,
}) : super(key: key);
@override
_EasyBuilderState<T> createState() => _EasyBuilderState<T>();
}
class _EasyBuilderState<T extends EasyXController> extends State<EasyBuilder<T>> {
late T controller;
@override
void initState() {
super.initState();
///此處是整個類的靈魂代碼
controller = Easy.find<T>(tag: widget.tag);
controller.xNotifier.addListener(() {
if (mounted) setState(() {});
});
}
@override
void dispose() {
if (widget.autoRemove) {
Easy.delete<T>(tag: widget.tag);
}
controller.xNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => widget.builder(controller);
}
使用
- 使用很簡單,先看下邏輯層
class EasyXCounterLogic extends EasyXController {
var count = 0;
void increase() {
++count;
update();
}
}
- 界面層
class EasyXCounterPage extends StatelessWidget {
final logic = Easy.put(EasyXCounterLogic());
@override
Widget build(BuildContext context) {
return BaseScaffold(
appBar: AppBar(title: const Text('EasyX-自定義EasyBuilder重繪機制')),
body: Center(
child: EasyBuilder<EasyXCounterLogic>(builder: (logic) {
return Text(
'點擊了 ${logic.count} 次',
style: TextStyle(fontSize: 30.0),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => logic.increase(),
child: Icon(Icons.add),
),
);
}
}
- 效果圖

InheritedWidget的實作
實作具有一定的難度 ??
更加詳細的決議可查看:Flutter Provider的另一面
先來看下InheritedWidget它自帶一些功能
- 儲存資料,且資料可以隨著父子節點傳遞
- 自帶區域重繪機制
資料傳遞

區域重繪
InheritedWidget對子節點的Element,有個強大的操作功能
- 可以將子widget的element實體,儲存在自身的InheritedElement中的_dependents變數中
- 呼叫其notifyClients方法,會遍歷_dependents中的子Element,然后呼叫子Element的markNeedsBuild方法,就完成了定點重繪子節點的操作

有了上面這倆個關鍵知識,就可以輕松的實作一個強大的狀態管理框架了,來看下實作
實作
- ChangeNotifierEasyP:類比Provider的ChangeNotifierProvider
class ChangeNotifierEasyP<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) {
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 {
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;
@override
InheritedElement createElement() => EasyPInheritedElement(this);
}
class EasyPInheritedElement<T extends ChangeNotifier> extends 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() {
if (_firstBuild) {
_firstBuild = false;
_value = (widget as EasyPInherited<T>).create(this);
_value.addListener(_callBack = () {
// 處理重繪邏輯,此處無法直接呼叫notifyClients
// 會導致owner!._debugCurrentBuildTarget為null,觸發斷言條件,無法向后執行
_shouldNotify = true;
markNeedsBuild();
});
}
super.performRebuild();
}
@override
Widget build() {
if (_shouldNotify) {
_shouldNotify = false;
notifyClients(widget);
}
return super.build();
}
@override
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
//此處就直接重繪添加的監聽子Element了,不各種super了
dependent.markNeedsBuild();
// super.notifyDependent(oldWidget, dependent);
}
@override
void unmount() {
_value.removeListener(_callBack);
_value.dispose();
super.unmount();
}
}
- EasyP:類比Provider的Provider類
class EasyP {
/// 獲取EasyP實體
/// 獲取實體的時候,listener引數老是寫錯,這邊直接用倆個方法區分了
static T of<T extends ChangeNotifier>(BuildContext context) {
return _getInheritedElement<T>(context).value;
}
/// 注冊監聽控制元件
static T register<T extends ChangeNotifier>(BuildContext context) {
var element = _getInheritedElement<T>(context);
context.dependOnInheritedElement(element);
return element.value;
}
/// 獲取距離當前Element最近繼承InheritedElement<T>的組件
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>';
}
- build:最后整一個Build類就行了
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();
}
}
大功告成,上面這三個類,就基于InheritedWidget自帶的功能,實作了一套狀態管理框架
- 實作了區域重繪功能
- 實作了邏輯層實體,可以隨著Widget父子節點傳遞功能
使用
用法基本和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('自定義狀態管理框架-EasyP范例')),
body: Center(
child: EasyPBuilder<CounterEasyP>(() {
return Text(
'點擊了 ${easyP.count} 次',
style: TextStyle(fontSize: 30.0),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => easyP.increment(),
child: Icon(Icons.add),
),
);
}
}
- easyP
class CounterEasyP extends ChangeNotifier {
int count = 0;
void increment() {
count++;
notifyListeners();
}
}
- 效果圖:

自動化重繪的實作
實作需要一些的靈感 ???
自動化重繪的實作
- 將單個狀態變數和重繪組件,建立起了連接
- 一但變數數值改變,重繪組件自動重繪
- 某狀態變化,只會自動觸發其重繪組件,其它重繪組件并不觸發
實作
同樣的,需要管理其邏輯類的中間件;為了范例完整,再寫下這個依賴管理類
///依賴注入,外部可將實體,注入該類中,由該類管理
class Easy {
///注入實體
static T put<T>(T dependency, {String? tag}) =>
_EasyInstance().put(dependency, tag: tag);
///獲取注入的實體
static T find<T>({String? tag, String? key}) =>
_EasyInstance().find<T>(tag: tag, key: key);
///洗掉實體
static bool delete<T>({String? tag, String? key}) =>
_EasyInstance().delete<T>(tag: tag, key: key);
}
///具體邏輯
class _EasyInstance {
factory _EasyInstance() => _instance ??= _EasyInstance._();
static _EasyInstance? _instance;
_EasyInstance._();
static final Map<String, _InstanceInfo> _single = {};
///注入實體
T put<T>(T dependency, {String? tag}) {
final key = _getKey(T, tag);
//只保存第一次注入:針對自動重繪機制優化,每次熱多載的時候,資料不會重置
_single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
return find<T>(tag: tag);
}
///獲取注入的實體
T find<T>({String? tag, String? key}) {
final newKey = key ?? _getKey(T, tag);
var info = _single[newKey];
if (info?.value != null) {
return info!.value;
} else {
throw '"$T" not found. You need to call "Easy.put($T())""';
}
}
///洗掉實體
bool delete<T>({String? tag, String? key}) {
final newKey = key ?? _getKey(T, tag);
if (!_single.containsKey(newKey)) {
print('Instance "$newKey" already removed.');
return false;
}
_single.remove(newKey);
print('Instance "$newKey" deleted.');
return true;
}
String _getKey(Type type, String? name) {
return name == null ? type.toString() : type.toString() + name;
}
}
class _InstanceInfo<T> {
_InstanceInfo(this.value);
T value;
}
- 自定義一個監聽類
class EasyXNotifier {
List<VoidCallback> _listeners = [];
void addListener(VoidCallback listener) => _listeners.add(listener);
void removeListener(VoidCallback listener) {
for (final entry in _listeners) {
if (entry == listener) {
_listeners.remove(entry);
return;
}
}
}
void dispose() => _listeners.clear();
void notify() {
if (_listeners.isEmpty) return;
for (final entry in _listeners) {
entry.call();
}
}
}
在自動重繪的機制中,需要將基礎型別進行封裝
- 主要邏輯在Rx中
- set value 和 get value是關鍵
///拓展函式
extension IntExtension on int {
RxInt get ebs => RxInt(this);
}
extension StringExtension on String {
RxString get ebs => RxString(this);
}
extension DoubleExtension on double {
RxDouble get ebs => RxDouble(this);
}
extension BoolExtension on bool {
RxBool get ebs => RxBool(this);
}
///封裝各型別
class RxInt extends Rx<int> {
RxInt(int initial) : super(initial);
RxInt operator +(int other) {
value = value + other;
return this;
}
RxInt operator -(int other) {
value = value - other;
return this;
}
}
class RxDouble extends Rx<double> {
RxDouble(double initial) : super(initial);
RxDouble operator +(double other) {
value = value + other;
return this;
}
RxDouble operator -(double other) {
value = value - other;
return this;
}
}
class RxString extends Rx<String> {
RxString(String initial) : super(initial);
}
class RxBool extends Rx<bool> {
RxBool(bool initial) : super(initial);
}
///主體邏輯
class Rx<T> {
EasyXNotifier subject = EasyXNotifier();
Rx(T initial) {
_value = initial;
}
late T _value;
bool firstRebuild = true;
String get string => value.toString();
@override
String toString() => value.toString();
set value(T val) {
if (_value == val && !firstRebuild) return;
firstRebuild = false;
_value = val;
subject.notify();
}
T get value {
if (RxEasy.proxy != null) {
RxEasy.proxy!.addListener(subject);
}
return _value;
}
}
需要寫一個非常重要的中轉類,這個也會儲存回應式變數的監聽物件
- 這個類有著非常核心的邏輯:他將回應式變數和重繪控制元件關聯起來了!
class RxEasy {
EasyXNotifier easyXNotifier = EasyXNotifier();
Map<EasyXNotifier, String> _listenerMap = {};
bool get canUpdate => _listenerMap.isNotEmpty;
static RxEasy? proxy;
void addListener(EasyXNotifier notifier) {
if (!_listenerMap.containsKey(notifier)) {
//變數監聽中重繪
notifier.addListener(() {
//重繪ebx中添加的監聽
easyXNotifier.notify();
});
//添加進入map中
_listenerMap[notifier] = '';
}
}
}
重繪控制元件Ebx
typedef WidgetCallback = Widget Function();
class Ebx extends StatefulWidget {
const Ebx(this.builder, {Key? key}) : super(key: key);
final WidgetCallback builder;
@override
_EbxState createState() => _EbxState();
}
class _EbxState extends State<Ebx> {
RxEasy _rxEasy = RxEasy();
@override
void initState() {
super.initState();
_rxEasy.easyXNotifier.addListener(() {
if (mounted) setState(() {});
});
}
Widget get notifyChild {
final observer = RxEasy.proxy;
RxEasy.proxy = _rxEasy;
final result = widget.builder();
if (!_rxEasy.canUpdate) {
throw 'Widget lacks Rx type variables';
}
RxEasy.proxy = observer;
return result;
}
@override
Widget build(BuildContext context) {
return notifyChild;
}
@override
void dispose() {
_rxEasy.easyXNotifier.dispose();
super.dispose();
}
}
在自動重繪機制中,回收依賴實體需要針對處理
此處我寫了一個回收控制元件,可以完成實體的自動回收
- 命名的含義,將實體和控制元件系結,控制元件被回收時,邏輯層實體也將被自動回收
class EasyBindWidget extends StatefulWidget {
const EasyBindWidget({
Key? key,
this.bind,
this.tag,
this.binds,
this.tags,
required this.child,
}) : assert(
binds == null || tags == null || binds.length == tags.length,
'The binds and tags arrays length should be equal\n'
'and the elements in the two arrays correspond one-to-one',
),
super(key: key);
final Object? bind;
final String? tag;
final List<Object>? binds;
final List<String>? tags;
final Widget child;
@override
_EasyBindWidgetState createState() => _EasyBindWidgetState();
}
class _EasyBindWidgetState extends State<EasyBindWidget> {
@override
Widget build(BuildContext context) {
return widget.child;
}
@override
void dispose() {
_closeController();
_closeControllers();
super.dispose();
}
void _closeController() {
if (widget.bind == null) {
return;
}
var key = widget.bind.runtimeType.toString() + (widget.tag ?? '');
Easy.delete(key: key);
}
void _closeControllers() {
if (widget.binds == null) {
return;
}
for (var i = 0; i < widget.binds!.length; i++) {
var type = widget.binds![i].runtimeType.toString();
if (widget.tags == null) {
Easy.delete(key: type);
} else {
var key = type + (widget.tags?[i] ?? '');
Easy.delete(key: key);
}
}
}
}
使用
- 邏輯層
class EasyXEbxCounterLogic {
RxInt count = 0.ebs;
///自增
void increase() => ++count;
}
- 界面層:頁面頂節點套了一個EasyBindWidget,可以保證依賴注入實體可以自動回收
class EasyXEbxCounterPage extends StatelessWidget {
final logic = Easy.put(EasyXEbxCounterLogic());
@override
Widget build(BuildContext context) {
return EasyBindWidget(
bind: logic,
child: BaseScaffold(
appBar: AppBar(title: const Text('EasyX-自定義Ebx重繪機制')),
body: Center(
child: Ebx(() {
return Text(
'點擊了 ${logic.count.value} 次',
style: TextStyle(fontSize: 30.0),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => logic.increase(),
child: Icon(Icons.add),
),
),
);
}
}
- 效果圖

最后
本文總體上,對狀態管理的各個層次劃分做了一些思考和一點個人的見解,文章后半截也給出了一些狀態管理的實作方案
文章里的內容對想設計狀態管理的靚仔,應該有一些幫助;如果你有相關不同的意見,歡迎在評論區討論

相關地址
- 文章demo地址:flutter_use
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/303338.html
標籤:其他
