请选择 进入手机版 | 继续访问电脑版

石家庄老站长

点击联系客服
客服QQ: 客服微信:
 找回密码
 立即注册
查看: 12|回复: 0

Flutter对状态管理的认识和思考

[复制链接]

1

主题

1

帖子

-7

积分

限制会员

积分
-7
发表于 2021-9-29 14:53:21 | 显示全部楼层 |阅读模式
前言

编程技术交流圣地[-Flutter集团-]启动的状态管理研究小组将对状态管理相关主题进行为期两个月的讨论。





目前,只有被内定的5人参与讨论。如果你对状态管理有什么独特的看法或想参与,请与张风承特烈商量。请与我们交流。(大卫亚设,北方执行部队。)

对于这篇文章的一些内容,我很久以前就想写了,但一直没有任何动力,一直在放鸽子

这次被喷气队长催了几次,终于写完了这篇文章,文章中有我对状态管理的想法和看法,希望在茫茫人群中引起零星的共鸣。(莎士比亚,哈姆雷特) 。











状态管理的认知

变迁

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

对于最经典的MVC,统一将模块分为三层。

Model层:数据管理控制器层:逻辑处理View层:视图构建经典的层次划分,可以处理很多场景

MVP、MVVM也是MVC的变种。本质上是为了在适当的场景中更加合理的解耦。

事实上,这些模式适用于移动端,移动端旧XML的写入是导入相应的视图节点,然后在该节点上工作。

在JSP时代,JQuery进行了大规模的行动,包括操作DOM节点和刷新数据。一样的。

时代总是在发展中前进,技术也在不停变迁;就像普罗米修斯盗火而来,给世间带来诸多变化

对视图节点操作的固定应用在今天的前端是不正确的

如今,前端以多种“状态”控制接口,需要用更时尚的语言来说明。

包容万千

状态管理的焦点在于表面:状态和管理。

但4个字,凶猛地概括思想和灵魂状态是页面的灵魂,是业务逻辑和通用逻辑的锚,只要分离和管理状态,就可以分离页面。(另一方面,它也是如此。)(另一方面,它也是如此。)。

一般来说,从状态管理的概述开始
念上,可以解耦出多个层级


极简模式 😃


这是一种十分简洁的层级划分,众多流行的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[B](context);
        return Scaffold(
          ...
          floatingActionButton: FloatingActionButton(
            //调用业务方法
            onPressed: () => bloc.increment(),
            child: Icon(Icons.add),
          ),
        );
      }
    }

  • Bloc层
    class BlBlocCounterBloc extends Bloc[B] {
      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[B](context);
        return Scaffold(
          ...
          floatingActionButton: FloatingActionButton(
            onPressed: () => bloc.add(AEvent()),
            child: Icon(Icons.add),
          ),
        );
      }
    }

  • Bloc层
    class BlBlocCounterBloc extends Bloc[B] {
      BlBlocCounterBloc() : super(BlBlocCounterState().init());
      @override
      Stream[B] 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 buildEffect() {
      return combineEffects(>{
        //初始化
        Lifecycle.initState: _init,
        //切换tab
        MainAction.selectTab: _selectTab,
        //选择相应抽屉内部的item
        MainAction.clickDrawer: _clickDrawer,
        //跳转搜索页面
        MainAction.toSearch: _toSearch,
      });
    }
    ///众多业务方法
    void _init(Action action, Context 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 buildReducer() {
      return asReducer(
        >{
          WebViewAction.onRefresh: _onRefresh,
        },
      );
    }
    WebViewState _onRefresh(WebViewState state, Action action) {
      return state.clone();
    }

    就算在复杂的模块,我也没感受到他给我带来的好处,我就只能把他无限弱化成一个刷新方法了



    状态管理的几种实现
    这是我看了一些状态管理的源码

  • 总结出的几种状态管理的刷新机制
  • 任选一种,都可以搓出你自己的状态管理框架
    之前的几篇源码剖析文章写过,整理了下,做个总结



    烂大街的实现

    实现难度最小


    这是一种非常常见的实现

  • 这是一种简单,易用,强大的实现
  • 同时由于难度不高,也是一种烂大街的实现
    实现

    需要实现一个管理逻辑层实例的的中间件:依赖注入的实现


    也可以使用InheritedWidget保存和传递逻辑层实例(Bloc就是这样做的);但是自己管理,可以大大拓宽使用场景,此处就自己实现一个管理实例的中间件

  • 这边只实现三个基础api
    ///依赖注入,外部可将实例,注入该类中,由该类管理
    class Easy {
      ///注入实例
      static T put(T dependency, {String? tag}) =>
          _EasyInstance().put(dependency, tag: tag);
      ///获取注入的实例
      static T find({String? tag, String? key}) =>
          _EasyInstance().find(tag: tag, key: key);
      ///删除实例
      static bool delete({String? tag, String? key}) =>
          _EasyInstance().delete(tag: tag, key: key);
    }
    ///具体逻辑
    class _EasyInstance {
      factory _EasyInstance() => _instance ??= _EasyInstance._();
      static _EasyInstance? _instance;
      _EasyInstance._();
      static final Map _single = {};
      ///注入实例
      T put(T dependency, {String? tag}) {
        final key = _getKey(T, tag);
        //只保存第一次注入:针对自动刷新机制优化,每次热重载的时候,数据不会重置
        _single.putIfAbsent(key, () => _InstanceInfo(dependency));
        return find(tag: tag);
      }
      ///获取注入的实例
      T find({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({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 {
      _InstanceInfo(this.value);
      T value;
    }


    定义一个监听和基类


  • 也可以使用ChangeNotifier;此处我们自己简单定义个
    class EasyXNotifier {
      List _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 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 createState() => _EasyBuilderState();
    }
    class _EasyBuilderState extends State> {
      late T controller;
      @override
      void initState() {
        super.initState();
           
        ///此处是整个类的灵魂代码
        controller = Easy.find(tag: widget.tag);
        controller.xNotifier.addListener(() {
          if (mounted) setState(() {});
        });
      }
      @override
      void dispose() {
        if (widget.autoRemove) {
          Easy.delete(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(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 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 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 extends InheritedElement {
      EasyPInheritedElement(EasyPInherited 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).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(BuildContext context) {
        return _getInheritedElement(context).value;
      }
      /// 注册监听控件
      static T register(BuildContext context) {
        var element = _getInheritedElement(context);
        context.dependOnInheritedElement(element);
        return element.value;
      }
      /// 获取距离当前Element最近继承InheritedElement的组件
      static EasyPInheritedElement
          _getInheritedElement(BuildContext context) {
        var inheritedElement = context
                .getElementForInheritedWidgetOfExactType>()
            as EasyPInheritedElement?;
        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';
    }

  • build:最后整一个Build类就行了
    class EasyPBuilder extends StatelessWidget {
      const EasyPBuilder(
        this.builder, {
        Key? key,
      }) : super(key: key);
      final Widget Function() builder;
      @override
      Widget build(BuildContext context) {
        EasyP.register(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(context);
        return Scaffold(
          appBar: AppBar(title: Text('自定义状态管理框架-EasyP范例')),
          body: Center(
            child: EasyPBuilder(() {
              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 dependency, {String? tag}) =>
          _EasyInstance().put(dependency, tag: tag);
      ///获取注入的实例
      static T find({String? tag, String? key}) =>
          _EasyInstance().find(tag: tag, key: key);
      ///删除实例
      static bool delete({String? tag, String? key}) =>
          _EasyInstance().delete(tag: tag, key: key);
    }
    ///具体逻辑
    class _EasyInstance {
      factory _EasyInstance() => _instance ??= _EasyInstance._();
      static _EasyInstance? _instance;
      _EasyInstance._();
      static final Map _single = {};
      ///注入实例
      T put(T dependency, {String? tag}) {
        final key = _getKey(T, tag);
        //只保存第一次注入:针对自动刷新机制优化,每次热重载的时候,数据不会重置
        _single.putIfAbsent(key, () => _InstanceInfo(dependency));
        return find(tag: tag);
      }
      ///获取注入的实例
      T find({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({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 {
      _InstanceInfo(this.value);
      T value;
    }

  • 自定义一个监听类
    class EasyXNotifier {
      List _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 {
      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 {
      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 {
      RxString(String initial) : super(initial);
    }
    class RxBool extends Rx {
      RxBool(bool initial) : super(initial);
    }
    ///主体逻辑
    class Rx {
      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 _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 {
      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? binds;
      final List? tags;
      final Widget child;
      @override
      _EasyBindWidgetState createState() => _EasyBindWidgetState();
    }
    class _EasyBindWidgetState extends State {
      @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  
    使用
  • 逻辑层
    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
  • 回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|无图版|手机版|小黑屋|石家庄@IT精英团

    GMT+8, 2021-10-18 00:23 , Processed in 0.218401 second(s), 19 queries .

    Powered by Discuz! X3.4

    © 2001-2021 Comsenz Inc.

    快速回复 返回顶部 返回列表