乐趣区

关于flutter: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),
        ),
      ),
    );
  }
}
  • 效果图

最初

本文总体上,对状态治理的各个档次划分做了一些思考和一点集体的见解,文章后半截也给出了一些状态治理的实现计划

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

相干地址

  • 文章 demo 地址:flutter_use
退出移动版