写点什么

Flutter 对状态管理的认知与思考

用户头像
小呆呆666
关注
发布于: 刚刚
Flutter 对状态管理的认知与思考

前言

编程技术交流圣地[-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); }}
复制代码


  • Event


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 层疯狂的套娃,做了很多思考,关于一些拆分形式做了一些尝试


拆分后的效果,将 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<T>中

  • 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), ), ), ); }}
复制代码


  • 效果图


最后

本文总体上,对状态管理的各个层次划分做了一些思考和一点个人的见解,文章后半截也给出了一些状态管理的实现方案


文章里的内容对想设计状态管理的靓仔,应该有一些帮助;如果你有相关不同的意见,欢迎在评论区讨论



相关地址


发布于: 刚刚阅读数: 2
用户头像

小呆呆666

关注

2021,葬爱不在低调 2020.08.17 加入

还未添加个人简介

评论

发布
暂无评论
Flutter 对状态管理的认知与思考