美文网首页
3.Flutte3.0 遥遥领先系列|一文教你完全掌握状态管理,

3.Flutte3.0 遥遥领先系列|一文教你完全掌握状态管理,

作者: 鹏城十八少 | 来源:发表于2024-01-28 20:23 被阅读0次

    重中之重, 源码解析: (state的demo和源码分析重点看看)

    目录

    1. StatelessWidget 与 StatefulWidget 区别? build方法
    2. 原始setstate的原理,状态管理的重要性 (重点)
    3. 官方 provider的原理解析 (重点)
      3.1重点的2种方式和源码分析 , (provider和InheritedWidget)
      3.2Provider的案例使用ChangeNotifier, ChangeNotifierProvider 实现2个组件之间的通信
      3.3Consumer 刷新指定区域
      3.4 Selector的用法
      3.5MultiProvider 多zhuang状态共享.
      事实上,当我们使用 Provider 后,我们就再也不需要使用 StatefulWidget 了。
    4. 手写简单版provider
    5. 基础的跨组件框架, InheritedWidget、Notification和EventBus
    6. 第三方框架图文比较, 咸鱼Fish Redux的框架 , getX的状态管理

    状态管理是什么:

    jietu-1706527334684.jpg

    Flutter的状态可以分为全局状态和局部状态两种。

    Flutter 状态管理是指在 Flutter 应用中有效地管理应用的数据和状态

    状态管理是声明式编程非常重要的一个概念

    问题: 为什么要做状态管理?

    就是有几个页面, 要实现数据的同步或者共享!

    下面是官方给出的一些原则可以帮助你做决定:

    • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
    • 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
    • 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。

    1. StatelessWidget 与 StatefulWidget 区别?

    StatelessWidget和StatefulWidget的区别就在这个可变的State了。

    当状态数据发生变化时,Flutter 会调用 build() 方法重新构建界面

    问题: build方法什么情况下被执行呢?:

    • 1)、当我们的StatelessWidget第一次被插入到Widget树中时(也就是第一次被创建时);
    • 2)、当我们的父Widget(parent widget)发生改变时,子Widget会被重新构建;
    • 3)、如果我们的Widget依赖InheritedWidget的一些数据,InheritedWidget数据发生改变时;

    Stateful widget特有:

    至少由两个类组成:

     一个StatefulWidget类。
    
     一个 State类; StatefulWidget类本身是不变的,但是State类中持有的状态在 widget 生命周期中可能会发生变化。
    

    问题: 为什么要将 build 方法放在 State 中,而不是放在StatefulWidget中?

    1).状态访问不便, 属性会被公开

    试想一下,如果我们的StatefulWidget有很多状态,而每次状态改变都要调用build方法,由于状态是保存在 State 中的,如果build方法在StatefulWidget中,那么build方法和状态分别在两个类中,那么构建时读取状态将会很不方便!

    试想一下,如果真的将build方法放在 StatefulWidget 中的话,由于构建用户界面过程需要依赖 State,所以build方法将必须加一个State参数,大概是下面这样:

      Widget build(BuildContext context, State state){
          //state.counter
          ...
      }
    

    这样的话就只能将State的所有状态声明为公开的状态,这样才能在State类外部访问状态!但是,将状态设置为公开后,状态将不再具有私密性,这就会导致对状态的修改将会变的不可控。但如果将build()方法放在State中的话,构建过程不仅可以直接访问状态,而且也无需公开私有状态,这会非常方便。

    2.继承StatefulWidget不便。

    例如,Flutter 中有一个动画 widget 的基类AnimatedWidget,它继承自StatefulWidget类。AnimatedWidget中引入了一个抽象方法build(BuildContext context),继承自AnimatedWidget的动画 widget 都要实现这个build方法。现在设想一下,如果StatefulWidget 类中已经有了一个build方法,正如上面所述,此时build方法需要接收一个 State 对象,这就意味着AnimatedWidget必须将自己的 State 对象(记为_animatedWidgetState)提供给其子类,因为子类需要在其build方法中调用父类的build方法,代码可能如下:

    问题: 为什么flutter在设计的时候statefulWidget的build方法放在state中?

    1. build依赖state中的变量
      2.widget会不停的销毁
    2. 专题改变, 不希望把state改变

    问题: build的context是什么

    在StatelessElement中,我们发现是将this传入,所以本质上BuildContext就是当前的Element
    context是当前 widget 在 widget 树中位置中执行”相关操作“的一个句柄(handle),比如它提供了从当前 widget 开始向上遍历 widget 树以及按照 widget 类型查找父级 widget 的方法

    class ContextRoute extends StatelessWidget  {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Context测试"),
          ),
          body: Container(
            child: Builder(builder: (context) {
              // 在 widget 树中向上查找最近的父级`Scaffold`  widget 
              Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
              // 直接返回 AppBar的title, 此处实际上是Text("Context测试")
              return (scaffold.appBar as AppBar).title;
            }),
          ),
        );
    
     // 查找父级最近的Scaffold对应的ScaffoldState对象
                      ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;
    

    如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State不希望暴露,则不提供of方法

      // 直接通过of静态方法来获取ScaffoldState
          ScaffoldState _state=Scaffold.of(context);
    

    StatelessWidget特有
    问题: 我之前说过定义到Widget中的数据都是不可变的,必须定义为final,为什么呢?
    Flutter如何做到我们在开发中定义到Widget中的数据一定是final的呢?

    @immutable
    abstract class Widget extends DiagnosticableTree {
        // ...省略代码
    }
    

    这里有一个很关键的东西@immutable

    • 我们似乎在Dart中没有见过这种语法,这实际上是一个 注解,这设计到Dart的元编程,我们这里不展开讲;

    • 来源: https://api.flutter.dev/flutt...

    • 说明: 被@immutable注解标明的类或者子类都必须是不可变的

    2. 原始setstate的原理,状态管理的重要性

    源码分析:

    setState 仅在本地范围内有效,如果一个 Widget 需要改变它自己的状态,那么 setState 就是你最好的选择

    setstate() 主要用于修改数据,变量值的! 相当于notifychange

    问题: 如何从statefullWidget把数据传递到state类中?

    可以2次传递

    还有一种方法,state中可以直接取到statefullWidget的实例_widget , 就可以!

    之前最简单的就是 widget, setstate

    那 State 是在哪里被创建的?

    class StatefulElement extends ComponentElement {
      StatefulElement(StatefulWidget widget)
          : _state = widget.createState(),
            super(widget) {
        assert(() {
          if (!state._debugTypesAreRight(widget)) {
            throw FlutterError.fromParts(<DiagnosticsNode>[
              ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
              ErrorDescription(
                'The createState function for ${widget.runtimeType} returned a state '
                'of type ${state.runtimeType}, which is not a subtype of '
                'State<${widget.runtimeType}>, violating the contract for createState.',
              ),
            ]);
          }
          return true;
        }());
        assert(state._element == null);
        state._element = this;
        state._widget = widget;
        assert(state._debugLifecycleState == _StateLifecycle.created);
      }
    

    更新ui, state是啥?

      @override
      void update(ProxyWidget newWidget) {
        final ProxyWidget oldWidget = widget as ProxyWidget;
        assert(widget != newWidget);
        super.update(newWidget);
        assert(widget == newWidget);
        updated(oldWidget);
        rebuild(force: true);
      }
    

    调用widget的setstate方法没, 会执行StatefulElement的update()方法

      @protected
      void setState(VoidCallback fn) {
        assert(() {
        final Object? result = fn() as dynamic;
        }());
        _element!.markNeedsBuild();  // markNeedsBuild () 
      }
    
     void markNeedsBuild() {
        assert(_lifecycleState != _ElementLifecycle.defunct);
        if (_lifecycleState != _ElementLifecycle.active) {
          return;
        }
        assert(owner != null);
        assert(_lifecycleState == _ElementLifecycle.active);
        assert(() {
          if (owner!._debugBuilding) {
            assert(owner!._debugCurrentBuildTarget != null);
            assert(owner!._debugStateLocked);
            if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) {
              return true;
            }
            final List<DiagnosticsNode> information = <DiagnosticsNode>[
              ErrorSummary('setState() or markNeedsBuild() called during build.'),
              ErrorDescription(
              ),
              describeElement('The widget on which setState() or markNeedsBuild() was called was'),
            ];
          return true;
        }());
        if (dirty) { // 这里进行了返回! 
          return;
        }
        _dirty = true;
        owner!.scheduleBuildFor(this);  // 调用scheduleBuildFor方法, 传入当前Element对象
      }
    
      void scheduleBuildFor(Element element) {
        assert(element.owner == this);
        assert(() {
          if (debugPrintScheduleBuildForStacks) {
            debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}');
          }
          return true;
        }());
        if (element._inDirtyList) {
          _dirtyElementsNeedsResorting = true;
          return;
        }
        if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
          _scheduledFlushDirtyElements = true;
          onBuildScheduled!();
        }
        _dirtyElements.add(element);  //添加到_dirtyElements集合中
        element._inDirtyList = true;
          return true;
        }());
      }
    

    从现象推断,整个流程必然会经过setState()-···················->当前State的build()-················->页面绘制-············->屏幕刷新
    问题: setState ()是如何更新UI的?
    setState源码分析总结:

    我们常说的 setState ,其实是调用了 markNeedsBuild ,markNeedsBuild 内部会标记 element 为 diry,添加到BuildOwner对象的_dirtyElements集合中, 然后调用scheduleFrame来注册Vsync回调。 当下一次vsync信号的到来时会执行handleBeginFrame()和handleDrawFrame()来更新UI。
    然后在下一帧 WidgetsBinding.drawFrame 才会被绘制,这可以也看出 setState 并不是立即生效的
    

    State#setState 的核心作用就是把持有的元素标脏并申请新帧调度。而只有新帧到来,执行完构建之后,元素的 dirty 才会置为 false 。也就是说,两帧之间,无论调用多少次 setState ,都只会触发一次, 元素标脏 和 申请新帧调度 。这就是为什么连续触发 1000000 次,并无大事发生的原因

    setState()会重建, 但是有dirty的判读, 不会经常重建!

    dirty state的含义是脏的State
    它实际是通过一个Element的东西(我们还没有讲到Flutter绘制原理)的属性来标记的;
    将它标记为dirty会等待下一次的重绘检查,强制调用build方法来构建我们的Widget
    setState可以分为两个部分:

    将element标脏
    渲染时将所有脏element都rebuild,且将自己的child进行update
    重要的方法: performRebuild()

    updateChild(_child, built, slot)

    问题: setState每次都会去执行build ()?

    父widget
    父widget2
    子widget3: 用state变量

    点击: 调用setstate方法, 然后会创建, 调用 build(). 是不是只重绘制子widget3, 其他的不会重新绘制
    setState 触发了对你当前所在的小组件的重建。如果你的整个应用程序只包含一个widget,那么整个widget将被重建,这将使你的应用程序变得缓慢
    要把setstate多封装一层, 让setstate咋你自己的widget, 这样就不会重建整个widget

    问题: 为什么高位置的setState ()会消耗性能?

    虽然setState的调用并没有像 Widget 层那样,在渲染控制层的 Element 那一层重新构建全部element。但是,这并不代表 setState 的使用没问题,首先,像之前篇章说的那样,它会重新构建整个 Widget 树,这会带来性能损耗;其次,由于整个 Widget 树改变了,意味着整棵树对应的渲染层Element对象都会执行 update方法,虽然不一定会重新渲染,但是这整棵树的遍历的性能开销也很高

    总结: 虽然每次不会都创建element, 但是遍历有损耗, 提升办法: 只更新需要更新的widget!
    当我们在一个高节点调用setState()的时候会构建再次build所有的Widget,虽然不一定挂载到Element树中,但是平时我们使用的Widget中往往嵌套多个其他类型的Widget,每个build()方法走下来最终也会带来不小的开销,因此通过各种状态管理方案,Stream等方式,只做局部刷新,是我们日常开发中应该养成的良好习惯。

    流转图:
    setState() 添加到_dirtyElements集合中, owner.scheduleBuildFor(this)
    rebuild()
    StatefulElement-->performRebuild(): 最重要的方法
    StatefulElement--->updateChild()
    Element---> update();

    build()过程虽然只是调用一个组件的构造方法,不涉及对Element树的挂载操作。

    但因为我们一个组件往往是N多个Widget的嵌套组合,每个都遍历一遍开销算下来并不小

    问题: setState()如何做优化?

    要在子控件中调用setState(),

    局部刷新 , 但是局部刷新,也只是 setState 的封装

    setstate.jpg

    3. 官方 provider: 复杂情况的状态管理

    **数据传递: 多个页面之间的传递, 数据变化通知! **

    Flutter 官方的状态管理框架 Provider 则相对简单得多

    Provider 是一个用来提供数据的框架。它是 InheritedWidget 的语法糖,提供了依赖注入的功能,允许在 Widget 树中更加灵活地处理和传递数据

    Provider 就是针对 InheritedWidget 的一个包装工具

    在使用Provider的时候,我们主要关心三个概念:

    • ChangeNotifier:真正数据(状态)存放的地方
    • ChangeNotifierProvider:Widget树中提供数据(状态)的地方,会在其中创建对应的ChangeNotifier
    • Consumer:Widget树中需要使用数据(状态)的地方 读Provider

    读和写的Provider
    ChangeNotifierProvider:Widget树中提供数据(状态)的地方,会在其中创建对应的ChangeNotifier
    但是,滥用 Provider.of 方法也有副作用,那就是当数据更新时,页面中其他的子 Widget 也会跟着一起刷新。
    可以看到,TestIcon 控件本来是一个不需要刷新的 StatelessWidget,但却因为其父 Widget FloatingActionButton 所依赖的数据资源 counter 发生了变化,导致它也要跟着刷新。

    Consumer: 消费, 把数据消费! (用的比 Provider.of 多)

    因为Consumer在刷新整个Widget树时,会尽可能少的rebuild Widget。
    在数据资源发生变化时,只刷新对资源存在依赖关系的 Widget,而其他 Widget 保持不变呢?
    有一个child().不重新构建
    Consumer是否是最好的选择呢?并不是,它也会存在弊端 Selector的选择 (使用的比较多)

    比如当点击了floatingActionButton时,我们在代码的两处分别打印它们的builder是否会重新调用;
    我们会发现只要点击了floatingActionButton,两个位置都会被重新builder;
    但是floatingActionButton的位置有重新build的必要吗?没有,因为它是否在操作数据,并没有展示;

    如何可以做到让它不要重新build了?使用Selector来代替Consumer

    所以在某些情况下,我们可以使用Selector来代替Consumer,性能会更高
    Selector和Consumer对比,不同之处主要是三个关键点:

    关键点1:泛型参数是两个
    泛型参数一:我们这次要使用的Provider
    泛型参数二:转换之后的数据类型,比如我这里转换之后依然是使用CounterProvider,那么他们两个就是一样的类型
    关键点2:selector回调函数
    转换的回调函数,你希望如何进行转换
    S Function(BuildContext, A) selector
    我这里没有进行转换,所以直接将A实例返回即可
    关键点3:是否希望重新rebuild
    这里也是一个回调函数,我们可以拿到转换前后的两个实例;
    bool Function(T previous, T next);
    因为这里我不希望它重新rebuild,无论数据如何变化,所以这里我直接return false;
    多状态的资源封装

    Provider 的另一个升级版 MultiProvider,来实现多个 Provider 的组合注入
    ScrollAwareImageProvider:
    ChangeNotifier:
    搞清楚TextField是怎么使用ChangeNotifier的了,为什么每次改变TextEditingController的text值,然后在TextField数据框里的数据也及时改变了,其实最后还是用到setState。
    原理:
    performRebuild() :该回调会在setState或者build的时候会触发;此处做了一个判断,只会在第一次build的时候触发 build()
    notifyDependent()

    问题: InheritedElement? 而不是用普通的Element?

    如Flutter SDK中正是通过InheritedWidget来共享应用主题(Theme)和Locale (当前语言环境)信息

    InheritedWidget: 作为根节点, 然后其他子节点, 就可以获取根节点的状态! 但是如果要更新的话, 还是的手动用setstate

    最后,我们重写了 updateShouldNotify 方法,这个方法会在 Flutter 判断 InheritedWidget 是否需要重建,从而通知下层观察者组件更新数据时被调用到。在这里,我们直接判断 count 是否相等即可。

    定义一个共享数据的InheritedWidget,需要继承自InheritedWidget

    • 这里定义了一个of方法,该方法通过context开始去查找祖先的HYDataWidget(可以查看源码查找过程)
    • updateShouldNotify方法是对比新旧HYDataWidget,是否需要对更新相关依赖的Widget

    InheritedWidget中的属性在子Widget中只能读,如果有修改的场景,我们需要把它和StatefulWidget中的State配套使用。

    InheritedWidget是Flutter中的一个功能型Widget,适用于在Widget树中共享数据的场景。通过它,我们可以高效地将数据在Widget树中进行跨层传递

    InheritedWidget使用方法

    可以看到InheritedWidget的使用方法还是比较简单的,无论Counter在CountContainer下层什么位置,都能获取到其父Widget的计数属性count,再也不用手动传递属性了。

    不过,InheritedWidget仅提供了数据读的能力,如果我们想要修改它的数据,则需要把它和StatefulWidget中的State配套使用

    使用场景:

    InheritedWidget的数据流动方式是从父Widget到子Widget逐层传递

    InheritedWidget共有两个方法

    1).createElement() (创建对应的Element)
    
    2).updateShouldNotify(covariant InheritedWidget oldWidget)
    

    问题: Flutter中的InheritedWidget的实现原理是怎么样的?
    InheritedWidget的原理:

    主要是观察模式的思想

    源码分析: (重点)

    class CountContainer extends InheritedWidget {
      // 方便其子 Widget 在 Widget 树中找到它
      static CountContainer of(BuildContext context) => context.inheritFromWidgetOfExactType(CountContainer) as CountContainer;
      
      final int count;
     
      CountContainer({
        Key key,
        @required this.count,
        @required Widget child,
      }): super(key: key, child: child);
     
      // 判断是否需要更新
      @override
      bool updateShouldNotify(CountContainer oldWidget) => count != oldWidget.count;
    }
    

    Provider 特点:
    因为 Provider 实际上是 InheritedWidget 的语法糖,所以通过 Provider 传递的数据从数据流动方向来看,是由父到子(或者反过来)。这时我们就明白了,原来需要把资源放到 FirstPage 和 SecondPage 的父 Widget,也就是应用程序的实例 MyApp 中(当然,把资源放到更高的层级也是可以的,比如放到 main 函数中)

    他最主要的功能:就是会调用Element的performRebuild()方法,然后触发ComponentElement的build()方法,最终触发_InheritedProviderScopeElement的build方法

    其他: markNeedsNotifyDependents, 我们使用 notifyListeners(),就会触发,这个回调
    provide不是调用的setstate()进行状态管理的么?

    那怎么会触发到performRebuild()这个方法了?

    当我们执行 ChangeNotifer 的 notifyListeners 时,就会最终触发 setState 更新。
    执行流程。
    ChangeNotifer------>notifyListeners
    setState()
    InheritedElement------>performRebuild
    InheritedElement------>build
    InheritedElement------>notifyListeners

    我们要知道一个前提:刷新Widget会先进入Element的rebuild方法。然后是performRebuild方法,这个方法Element没做什么,交由具体子类去实现
    provider代码原理总结:

    wiget是InheritedWidget
    

    1)、 Provider 的内部 DelegateWidget 是一个 StatefulWidget ,所以可以更新且具有生命周期。
    2)、状态共享是使用了 InheritedProvider 这个 InheritedWidget 实现的。

    4.手写简单版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),
          ),
        );
    

    ChangeNotifier

    class CounterEasyP extends ChangeNotifier {
      int count = 0;
    
      void increment() {
        count++;
        notifyListeners();
      }
    }
    

    核心类: 3个
    ChangeNotifierProvider: 最重要的方法

    class ChangeNotifierProvider<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) {  // 重写 build()方法, 返回InheritedWidget对象
        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 { // 实现一个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;  // 重写updateShouldNotify方法
    
      @override
      InheritedElement createElement() => EasyPInheritedElement(this);
    }
    
    class EasyPInheritedElement<T extends ChangeNotifier> extends InheritedElement { // 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() { // 实现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() {  // build()重写
        if (_shouldNotify) {
          _shouldNotify = false;
          notifyClients(widget as EasyPInherited<T>);
        }
        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();
      }
    }
    

    Provider

    class Provider { // 不包含核心逻辑,仅仅是封装 
      /// 获取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>的组件
      //调用dependOnInheritedWidgetOfExactType() 和 getElementForInheritedWidgetOfExactType()的区别就是前者会注册依赖关系,而后者不会
      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>';
    }
    

    builder :

    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();
      }
    }
    

    案例:

    自己写一个封装的controller()

    3个核心类:

    build:

    Provider:

    ChangeNotifierProvider:

    点击事件: 数据变化调用provide里面的变化方法,

    ui更新, 也是拿provider的值

     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),
          ),
        );
      }
    }
    

    创建了一个widget:

    class CounterEasyPPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierEasyP(
          create: (BuildContext context) => CounterEasyP(),
          builder: (context) => _buildPage(context),
        );
      }
    

    把需要更新的widget传入到构造方法中去了! ChangeNotifier传入, buildcontext也在

    5. 简单的状态管理:

    对于数据的跨层传递,Flutter还提供了三种方案:InheritedWidget、Notification和EventBus。接下来,我将依次为你讲解这三种方案。


    b2a78dbefdf30895504b2017355ae066.png

    5.1 InheritedWidget 原理如上

    5.2 Notification

    数据流动方式是从子Widget向上传递至父Widget。这样的数据传递机制适用于子Widget状态变更,发送通知上报的场景

    Notification是一种用于在小部件树中传递信息的机制,它可以用于实现子树中的特定部分之间的通信。Notification并不像状态管理或全局状态传递那样普遍,它主要用于特定场景下的通信,比如当某个事件发生时,需要在小部件树的各个部分之间传递消息。Notification的工作方式是通过Notification对象在小部件树中传递,然后从父级小部件开始逐级向上冒泡,直到找到一个处理该通知的小部件为止。每个处理通知的小部件可以根据需要执行特定的操作。你可以把InheritedWidget 理解为从上到下传递、共享的方式,而Notification则是从下往上。Notification它提供了dispatch方法,沿着context对应的Element节点向上逐层发送通知。

    跨组件事件传递

    5.3 EventBus

    在组件之间如果有事件需要传递,一方面可以一层层来传递,另一方面我们也可以使用一个EventBus工具来完成。

    其实EventBus在Vue、React中都是一种非常常见的跨组件通信的方式:

    EventBus相当于是一种订阅者模式,通过一个全局的对象来管理;

    这个EventBus我们可以自己实现,也可以使用第三方的EventBus;

        无需发布者与订阅者之间存在父子关系的数据同步机制。
    

    无论是InheritedWidget还是Notificaiton,它们的使用场景都需要依靠Widget树,也就意味着只能在有父子关系的Widget之间进行数据共享。但是,组件间数据传递还有一种常见场景:这些组件间不存在父子关系。这时,事件总线EventBus就登场了。

         事件总线是在Flutter中实现跨组件通信的机制。它遵循发布/订阅模式,允许订阅者订阅事件,当发布者触发事件时,订阅者和发布者之间可以通过事件进行交互。发布者和订阅者之间无需有父子关系,甚至非Widget对象也可以发布/订阅。这些特点与其他平台的事件总线机制是类似的。
    
         总结:  
    

    这里我准备了一张表格,把属性传值、InheritedWidget、Notification与EventBus这四种数据共享方式的特点和使用场景做了简单总结,供你参考:

    6. 下面是对Provider、BLoC、Redux、GetX、Riverpod和MobX等Flutter状态管理库的一些对比:

    状态管理比较.jpg

    `1). InheritedWidget:在 Flutter 中,所有 Widget 都是通过父 Widget 构建出来的,父 Widget 可以通过 InheritedWidget 共享状态给子 Widget,子 Widget 可以通过调用 InheritedWidget.of() 方法来获取共享的状态。

    2). Provider:

    • 适用场景: 适用于中小型应用,需要在多个层级共享状态的场景。
    • 特点: 轻量级,使用InheritedWidget来共享状态,支持各种类型的状态,易于上手。
    • 优势: 简单易用,不需要大量的额外代码,具有高性能,适用于简单的状态共享。
    • 劣势: 在大型应用中可能难以管理复杂的状态。

    状态管理混乱,虽然用了 provider 来做状态管理,但一些代码如:异步请求、事件响应等还是会掺杂在UI页面的代码里面,一旦页面的各种 Widget 多了起来之后,显得非常严重,而且对业务逻辑的测试也不方便,多个组件可能需要共享相同的数据或状态,需要在每个组件中分别创建 Provider 实例,容易导致代码冗余,如果只需要更新页面的部分 Widget 使用Provider 还会导致嵌套过深,也可能导致性能问题、状态不一致以及难以追踪的错误

    3). ScopedModel:ScopedModel 也可以实现状态共享,但它的思想是将数据放在一个共享的 Model 中,然后让需要用到这些数据的 Widget 注册监听该 Model,当 Model 的数据改变时,通知监听它的 Widget 更新。
    4). Stream

    Stream是一种用于在应用程序中管理状态和数据流的重要工具。Stream是异步数据流的抽象表示,它可以在应用程序中传递和监听数据的变化。但是它和Flutter关系并不大,它是通过纯dart去实现的。你可以理解为flutter只是通过StreamBuilder去构建了一个Stream通道。它的使用其实也并没有复杂太多,通常只需要创建StreamController,然后去监听控制器(可以直接去监听StreamController,然后通过setState更新UI,也可以通过StreamBuilder),最后将更新后的数据通过Stream的sink属性添加到Stream中即可。知名的状态管理库Bloc,就是基于Stream的封装。

    5). BLoC:

    BLoC 算是 Flutter 早期比较知名的状态管理框架, 它是基于事件驱动来实现的状态管理

    BLoC 是业务逻辑组件的缩写,它使用 Streams 和Provider 库将业务逻辑和 UI 分离开来,可以用来管理状态和处理用户输入。作者在这里使用了Bloc用于状态管理

    基于 Stream 的封装可以更方便做一些事件状态的监听和转换

    • 适用场景: 适用于复杂的应用,需要分离业务逻辑和UI的场景。
    • 特点: 通过Streams管理状态和业务逻辑,将界面层与业务逻辑层分开,适合中大型应用。
    • 优势: 适合处理复杂的状态变化和异步操作,便于测试和维护。
    • 劣势: 在简单应用中可能显得过于复杂。需要写更多的代码,开发节奏会有点影响

    6). Redux:

    • Redux 是一种状态管理模式,它将状态和状态更新封装在一个可预测的单向数据流中,可以用于处理应用程序的复杂状态。

    前端开始者对 redux 可能会更熟悉一些

    在 flutter_redux 中,开发者的每个操作都只是一个 Action ,而这个行为所触发的逻辑完全由 middlewarereducer 决定

    • 适用场景: 适用于需要管理大量复杂状态的应用。
    • 特点: 基于单一状态源和不可变状态,通过Actions和Reducers来管理状态变化。
    • 优势: 严格的状态管理,适用于大型应用,具有强大的开发工具和中间件。
    • 劣势: 在小型应用中可能过于繁琐,学习曲线较陡`
      7). ;不在维护了2012年后没有维护了! 第三方框架, 咸鱼Fish Redux的框架

    8). Riverpod:

    适用场景: 适用于需要更强大、更简单的状态管理和依赖注入的场景。
    特点: 基于Provider的升级版本,提供更简单、更强大的API,支持多种状态管理模式。
    优势: 代码清晰,性能高效,支持多种状态管理模式,适用于各种规模的项目。
    劣势: 相对较新的库,社区可能还在成长。
    随着 Flutter 的发展,这些年 Flutter 上的状态管理框架如“雨后春笋”般层出不穷,而近一年以来最受官方推荐的状态管理框架无疑就是 Riverpod
    直观的就是 Riverpod 中的 Provider 可以随意写成全局, 如果说 Riverpod 最明显的特点是什么,那就是外部不依赖 BuildContext
    它的作者也是 Provider 的作者,同时也是 Flutter 官方推荐的状态管理库
    如何实现不依赖 BuildContext?
    在 Riverpod 里基本是每一个 “Provider” 都会有一个自己的 “Element” ,然后通过 WidgetRef 去 Hook 后成为 BuildContext 的替代,所以这就是 Riverpod 不依赖 Context 的 “魔法” 之一
    9). GetX:

    适用场景: 适用于快速开发和中小型应用,需要轻量级状态管理和依赖注入的场景。
    特点: 简单易用,提供状态管理、依赖注入和路由导航的综合解决方案。
    优势: 低学习曲线,高性能,适用于快速迭代的小型项目。
    劣势: 对于大型复杂应用,可能需要更复杂的状态管理方案。

    10). MobX:
    适用场景: 适用于需要响应式编程和可观察对象的场景。
    特点: 通过可观察对象和反应式编程来管理状态,支持多种数据变化方式。提高开发效率
    优势: 简化了状态管理,具有响应式编程的特点,易于学习和使用。
    劣势: 相对较新的库,可能在一些大型项目中缺乏一些高级功能。全家桶,做的太多对于一些使用者来说是致命缺点,需要解决的 Bug 也多
    11). rxdart: 太老了

    状态管理.jpg

    相关文章

      网友评论

          本文标题:3.Flutte3.0 遥遥领先系列|一文教你完全掌握状态管理,

          本文链接:https://www.haomeiwen.com/subject/ioowodtx.html