Flutter 对状态管理的认知与思考
前言
关于这篇文章的一些内容,我很久之前就想写的,但一直没啥源动力,就一直鸽着
这次被捷特大佬催了几次,终于把这篇文章写完了,文章里有我对状态管理的一些思考和看法,希望能引起茫茫人海中零星的共鸣。。。
状态管理的认知
变迁
解耦是众多思想或框架的基石
就拿最最最经典的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