Flutte3.0 遥遥领先系列|一文教你完全掌握状态管理, 手写简单版provider

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

状态管理是什么:

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的元编程,我们这里不展开讲;

  • 说明: 被@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 的封装

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() (创建对应的Element2).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。接下来,我将依次为你讲解这三种方案。

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状态管理库的一些对比:

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: 太老了

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值