flutter状态管理

137 阅读7分钟

在平时开发过程中,“状态管理”这个概念相信对大家而言并不陌生,想针对几种常用的状态管理做一个简单的使用介绍、源码剖析以及差异区分,有兴趣的小伙伴可以一起探究~

一、setState

使用方法:

  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Counter: $_counter'),
            RaisedButton(
              onPressed: _incrementCounter,
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

源码剖析:

void setState(VoidCallback fn) {
  _element.markNeedsBuild();
}

setState()方法内部调用了_element.markNeedsBuild()方法,该方法用于标记当前State对象所对应的Element为“dirty”状态,表示状态已经发生变化,需要重新构建UI。

void markNeedsBuild() {
  _dirty = true;
  _owner.scheduleBuildFor(this);
}

markNeedsBuild()方法会将_dirty属性设置为true,表示当前Element需要重新构建UI。 然后,它会调用_owner.scheduleBuildFor(this)方法,将当前Element添加到_owner,方便在下一帧进行重建

void scheduleBuildFor(Element element) {
  assert(element.owner == this);
  assert(() {
    if (debugPrintScheduleBuildForStacks) {
      debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}');
    }
    if (!element.dirty) {
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('scheduleBuildFor() called for a widget that is not marked as dirty.'),
        element.describeElement('The method was called for the following element'),
        ErrorDescription(
          'This element is not currently marked as dirty. Make sure to set the dirty flag before '
          'calling scheduleBuildFor().',
        ),
        ErrorHint(
          'If you did not attempt to call scheduleBuildFor() yourself, then this probably '
          'indicates a bug in the widgets framework. Please report it:
&nbsp;'
          '  https://github.com/flutter/flutter/issues/new?template=2_bug.yml',
        ),
      ]);
    }
    return true;
  }());
  if (element._inDirtyList) {
    assert(() {
      if (debugPrintScheduleBuildForStacks) {
        debugPrintStack(label: 'BuildOwner.scheduleBuildFor() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements');
      }
      if (!_debugIsInBuildScope) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('BuildOwner.scheduleBuildFor() called inappropriately.'),
          ErrorHint(
            'The BuildOwner.scheduleBuildFor() method should only be called while the '
            'buildScope() method is actively rebuilding the widget tree.',
          ),
        ]);
      }
      return true;
    }());
    _dirtyElementsNeedsResorting = true;
    return;
  }
  if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
    _scheduledFlushDirtyElements = true;
    onBuildScheduled!();
  }
  _dirtyElements.add(element);
  element._inDirtyList = true;
  assert(() {
    if (debugPrintScheduleBuildForStacks) {
      debugPrint('...dirty list is now: $_dirtyElements');
    }
    return true;
  }());
}

首先进行一些断言操作检查 如果Element对象没有被标记为dirty,则会抛出一个异常,提示调用者在调用scheduleBuildFor()之前应该先将Element标记为dirty。 如果Element对象已经在构建队列中,则会将_dirtyElementsNeedsResorting设置为true,表示需要对构建队列进行排序。 如果_scheduledFlushDirtyElementsfalseonBuildScheduled回调函数不为空,则会将_scheduledFlushDirtyElements设置为true,并调用onBuildScheduled回调函数。 将传入的Element对象添加到构建队列_dirtyElements中,并将_inDirtyList属性设置为true,表示该Element对象已经在构建队列中。

源码理解:

当调用setState()时,Flutter框架会执行以下步骤来刷新状态并重新构建UI:

  1. setState()方法接收一个回调函数作为参数,该回调函数用于更新状态。
  2. 在回调函数中,您可以修改状态变量的值。
  3. 一旦状态变量的值发生变化,Flutter框架会标记当前的Widget为“脏”(dirty)。
  4. 在下一个绘制帧(frame)之前,Flutter框架会调用build()方法来重新构建Widget树。
  5. build()方法中,Flutter框架会根据新的状态值来生成新的Widget树。
  6. 新的Widget树会与之前的Widget树进行比较,找出差异(diff)。
  7. Flutter框架会将差异应用到渲染树(render tree)中,只更新发生变化的部分。
  8. 最终,更新后的UI会在屏幕上呈现给用户。

优缺点:

1.小巧、使用方式简单

2.代码冗余:当应用程序变得复杂时,可能会有多个Widget需要共享相同的状态。使用setState()时,需要在每个Widget中手动调用setState()来更新状态并重新构建UI。这可能导致代码冗余,因为相同的状态更新逻辑需要在多个地方重复编写。

3.难维护:每个widget可能都需要状态更新,当遍布全局,很难理解整个程序的状态流转状态

4.使用时不宜层级过深,再进行diff时,进行全局搜索,层级过深会导致搜索时间过长,性能较低

二、Provider

使用方法:

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => Counter(),
      child: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Consumer<Counter>(
                builder: (context, counter, child) {
                  return Text('Counter: ${counter.count}');
                },
              ),
              RaisedButton(
                onPressed: () {
                  Provider.of<Counter>(context, listen: false).increment();
                },
                child: Text('Increment'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

实现原理:

  1. Provider的核心是InheritedProvider类,它继承自InheritedWidgetInheritedProvider通过继承InheritedWidget,将状态提升到整个Widget树的顶层,并在需要访问该状态的地方进行共享。
  2. InheritedProvider的构造函数接收一个泛型参数T,表示要共享的状态类型。它还接收一个create回调函数,用于创建和初始化状态对象。
  3. InheritedProvider重写了updateShouldNotify方法,用于判断是否需要通知子Widget进行更新。它通过比较新旧状态对象是否相等来决定是否需要更新。
  4. InheritedProvider还提供了一个静态方法of,用于获取共享的状态对象。它通过context.dependOnInheritedWidgetOfExactType方法来获取最近的InheritedProvider实例,并返回其中的状态对象。

接下来,我们来看一下Consumer的源码部分:

  1. Consumer是一个Widget,它接收一个builder回调函数作为参数。builder函数接收两个参数:contextvaluecontext是当前BuildContext,value是共享的状态对象。
  2. Consumer在build方法中调用builder函数,并将context和共享的状态对象传递给它。这样,我们可以在builder函数中根据状态对象来构建UI。
  3. Consumer通过InheritedProviderof方法来获取共享的状态对象,并将其传递给builder函数。这样,当状态对象发生变化时,Consumer会自动重新构建UI。

通过以上的源码分析,我们可以了解到Provider和Consumer之间的工作流程:

  1. 在顶层Widget中创建InheritedProvider,并将共享的状态对象传递给它。
  2. 在需要访问共享状态的地方,使用Consumer来获取共享的状态对象,并在builder函数中根据状态对象构建UI。
  3. 当共享的状态对象发生变化时,InheritedProvider会通知Consumer进行更新,从而重新构建UI。

这种基于InheritedWidget的机制,使得Provider和Consumer能够实现状态的共享和更新UI的自动化。通过依赖管理和局部更新的优化,Provider和Consumer提供了一种高效、灵活和可扩展的方式来进行状态管理。

优缺点:

1.局部更新:用Provider时,只有订阅了状态的部分会被更新,从而减少了不必要的重建和比较,提高了性能。

2.状态共享:Provider提供了一种简单而强大的方式来共享状态。通过在顶层Widget中创建Provider,可以将状态提升到整个应用程序的顶层,并在任何需要访问该状态的地方使用Consumer来获取和使用它。这样可以避免状态的传递和管理的复杂性,使代码更加简洁和可维护,可以通过Provider.of(context)访问

3.对于小型数据变化,可以考虑使用ValueNotifier

三、ValueNotifier

使用方法:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // 创建一个监听实例,在需要监听这个改变的地方使用ValueListenableBuilder去包裹你的Widget
  final ValueNotifier<int> counter = ValueNotifier<int>(0);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ValueNotifier Example',
      home: Scaffold(
        appBar: AppBar(
          title: Text('ValueNotifier Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ValueListenableBuilder<int>(
                valueListenable: counter,
                builder: (context, value, child) {
                  return Text(
                    'Count: $value',
                    style: TextStyle(fontSize: 24),
                  );
                },
              ),
              SizedBox(height: 16),
              RaisedButton(
                child: Text('Increment'),
                onPressed: () {
                  counter.value++;
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

源码:

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  /// Creates a [ChangeNotifier] that wraps this value.
  ValueNotifier(this._value) {
    if (kFlutterMemoryAllocationsEnabled) {
      ChangeNotifier.maybeDispatchObjectCreation(this);
    }
  }

  /// The current value stored in this notifier.
  ///
  /// When the value is replaced with something that is not equal to the old
  /// value as evaluated by the equality operator ==, this class notifies its
  /// listeners.
  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue) {
      return;
    }
    _value = newValue;
    notifyListeners();
  }

  @override
  String toString() => '${describeIdentity(this)}($value)';
}
abstract class ValueListenable<T> extends Listenable {
 /// Abstract const constructor. This constructor enables subclasses to provide
 /// const constructors so that they can be used in const expressions.
 const ValueListenable();

 /// The current value of the object. When the value changes, the callbacks
 /// registered with [addListener] will be invoked.
 T get value;
}
abstract class Listenable {
 /// Abstract const constructor. This constructor enables subclasses to provide
 /// const constructors so that they can be used in const expressions.
 const Listenable();

 /// Return a [Listenable] that triggers when any of the given [Listenable]s
 /// themselves trigger.
 ///
 /// The list must not be changed after this method has been called. Doing so
 /// will lead to memory leaks or exceptions.
 ///
 /// The list may contain nulls; they are ignored.
 factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable;

 /// Register a closure to be called when the object notifies its listeners.
 void addListener(VoidCallback listener);

 /// Remove a previously registered closure from the list of closures that the
 /// object notifies.
 void removeListener(VoidCallback listener);
}
  1. ValueNotifier类:

    • ValueNotifier是一个继承自ChangeNotifier的类,同时实现了ValueListenable接口。
    • 构造函数ValueNotifier(this._value)用于创建一个ValueNotifier对象,并初始化内部的_value变量。
    • value属性用于获取当前存储在ValueNotifier中的值。
    • value属性的setter方法用于设置新的值,并在新值与旧值不相等时通知监听器。
    • toString()方法用于返回ValueNotifier对象的字符串表示,包括对象的标识符和当前的值。
  2. ValueListenable抽象类:

    • ValueListenable是一个抽象类,继承自Listenable接口。
    • ValueListenable定义了一个抽象的value属性,用于获取当前对象的值。
    • 当值发生变化时,通过调用addListener注册的回调函数来通知监听器。
  3. Listenable抽象类:

    • Listenable是一个抽象类,定义了对象可以被监听的接口。
    • Listenable提供了addListenerremoveListener方法,用于注册和移除监听器。
    • Listenable还提供了一个静态工厂方法merge,用于创建一个Listenable对象,当给定的Listenable对象触发时,该对象也会触发。

所以,ValueNotifier是一个实现了ValueListenable接口的类,它可以存储一个值,并在值发生变化时通知监听器。ValueListenable是一个抽象类,定义了获取当前值和通知监听器的接口。Listenable是一个抽象类,定义了对象可以被监听的接口,并提供了注册和移除监听器的方法。这些类的设计使得我们可以轻松地实现值的监听和通知机制。

优缺点:

1.简单易用、更轻量
2.适用范围较狭窄

四、Getx

1.Obx

使用方法:

 class CounterPage extends StatelessWidget {

  var count = 0.obs;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Counter")),
      body: Center(
        child: Obx(() {
          return Text("${count.value}", style: const TextStyle(fontSize: 50),);
        }),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: (){
          count += 1;
        },
      ),
    );
  }
}

部分源码:

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  @override
  Widget build() => builder();
}
abstract class ObxWidget extends StatefulWidget {
 const ObxWidget({Key? key}) : super(key: key);

 @override
 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
   super.debugFillProperties(properties);
   properties..add(ObjectFlagProperty<Function>.has('builder', build));
 }

 @override
 _ObxState createState() => _ObxState();

 @protected
 Widget build();
}
class _ObxState extends State<ObxWidget> {
 final _observer = RxNotifier();
 late StreamSubscription subs;

 @override
 void initState() {
   super.initState();
   subs = _observer.listen(_updateTree, cancelOnError: false);
 }

 void _updateTree(_) {
   if (mounted) {
     setState(() {});
   }
 }

 @override
 void dispose() {
   subs.cancel();
   _observer.close();
   super.dispose();
 }

 @override
 Widget build(BuildContext context) =>
     RxInterface.notifyChildren(_observer, widget.build);
}

实现原理:

Obx的工作原理如下:

  1. Obx小部件接收一个回调函数作为参数,该回调函数返回一个可观察的数据对象。
  2. Obx小部件被构建时,它会订阅可观察对象的变化。
  3. 当可观察对象发生变化时,Obx小部件会被重新构建。
  4. 在重新构建时,Obx小部件会重新调用回调函数,并根据新的数据值来更新UI。
  5. Obx小部件只会更新与可观察对象相关的部分UI,而不会重新构建整个小部件树,从而提高性能。

在GetX中,可观察对象通常是通过Rx类来创建的,例如RxIntRxString等。这些可观察对象具有内置的监听机制,可以在其值发生变化时通知监听器。

Obx小部件通过订阅可观察对象的变化,实现了对数据的监听。当可观察对象的值发生变化时,Obx小部件会被重新构建,并根据新的数据值来更新UI。

需要注意的是,为了使Obx小部件能够正确地监听数据变化并进行更新,需要确保可观察对象的变化是在正确的地方进行的。通常情况下,应该在GetX的控制器类中使用可观察对象,并在需要更新UI的地方进行相应的变化操作。

总结起来,Obx小部件通过订阅可观察对象的变化,实现了对数据的监听,并在数据变化时正确地更新UI。这种机制使得GetX能够高效地进行状态管理和UI更新。

优缺点:

1.自动通知、简单易懂
2.局部性,无法全局共享状态

2.GetBuilder

使用:


class CounterController extends GetxController {
  var count = 0;

  void increment() {
    count.value++;
    update();
  }
}

class CounterPage extends StatelessWidget {
  final CounterController counterController = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter'),
      ),
      body: Center(
        child: GetBuilder<CounterController>(
          builder: (controller) {
            return Text(
              'Count: ${controller.count.value}',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counterController.increment();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

实现原理:

1.内部使用GetxController的观察者模式,当相关数据(Observeable)发生变化时,GetBuilder进行重建 .... 未完待续 优缺点

  1. 简化状态管理:GetBuilder通过封装状态管理和UI更新的逻辑,使得状态管理变得简单和直观。它提供了一个便捷的方式来管理和更新状态,减少了手动调用setState的复杂性。
  2. 数据驱动的UI更新:GetBuilder通过监听GetxController的状态变化,实现了数据驱动的UI更新。当状态发生变化时,GetBuilder会自动更新UI,无需手动触发UI更新的操作。
  3. 灵活性:GetBuilder可以根据需要创建全局或局部的GetxController实例。它可以灵活地管理不同范围的状态,并且可以在需要时进行销毁和重新创建。
  4. 可组合性:GetBuilder可以嵌套使用,形成状态管理的层次结构。这样可以更好地组织和管理复杂的应用程序状态。
  5. 轻量级:GetBuilderGet库中的一个小部件,它的实现相对简单,不会引入过多的复杂性和性能开销。

GetBuilder的缺点包括:

  1. 学习曲线:对于初学者来说,理解和使用GetBuilder可能需要一些时间和学习成本。尤其是对于那些没有使用过类似状态管理库的开发者来说,需要花费一些时间来理解其工作原理和使用方法。
  2. 依赖于Get库:GetBuilderGet库的一部分,因此使用GetBuilder需要引入Get库作为依赖。这可能会增加应用程序的包大小,并且需要额外的配置和集成工作。
  3. 限制于GetxControllerGetBuilder只能与GetxController一起使用,无法与其他状态管理库或自定义状态管理方案兼容。这可能会限制开发者在选择状态管理方案时的灵活性。

总的来说,GetBuilder是一个简化状态管理和实现数据驱动UI更新的实用工具。它提供了一种方便和灵活的方式来管理和更新状态,但也需要开发者花费一些时间来学习和理解其使用方法。