Flutter基建 - 神奇的状态管理State

Flutter 3.16.5 • channel stable • github.com/flutter/flu...

Framework • revision 78666c8dc5 (3 months ago) • 2023-12-19 16:14:14 -0800

Engine • revision 3f3e560236

Tools • Dart 3.2.3 • DevTools 2.28.4

本篇为Flutter基建的第十一篇文章💪🏻💪🏻💪🏻,本系列也即将进入尾声了(后续会逐步以一些日常实例和源码相关知识为主),本篇会带着小伙伴们一起了解下Flutter中状态管理相关知识,主要围绕着State和InheritedWidget知识点切入,下面我们一起进入文章感受下吧~

Flutter基建系列文章

Flutter基建 - Dart基础类型

Flutter基建 - Dart方法和类

Flutter基建 - 文本组件

Flutter基建 - 按钮全解析

Flutter基建 - 布局组件全面解析

Flutter基建 - 离不开的列表组件

Flutter基建 - 形影不离的State**Widget

Flutter基建 - 异步加载UI

Flutter基建 - 12种隐式动画小组件全解析

Flutter基建 - Hero动画有多英雄

Flutter基建 - 状态管理State

SetState管理状态

SetState方式是Flutter中最基础的状态管理方式了,在我们每次创建一个新项目的时候,都会看到官方的示例中包含了SetState用法,也就是计数器Demo,下面介绍所有的状态管理代码都采用计时器方式,来一起更深入的了解下Flutter中状态管理的几种方式。

scala 复制代码
class MySetStateWidget extends StatefulWidget {
  const MySetStateWidget({super.key});

  @override
  State<StatefulWidget> createState() {
    return _MySetState();
  }
}

class _MySetState extends State<MySetStateWidget> {
  int _count = 0;

  void _incrementCount(_) {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return buildScaffold(
      context,
      Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          InkWell(
            onTap: () {
              _incrementCount(null);
            },
            child: Text("count: $_count", style: commonTS()),
          ),
        ],
      ),
    );
  }
}

这是一个最简单最常用的例子,点击按钮_count值就会自增,这块相信小伙伴们都是一眼就懂的,下面我们将例子稍微增加点,增加一个子Widget,同时将_count传递过去,并且提供一个自增方法的回调,让子Widget也可以改变我们的状态。

scala 复制代码
children: [
  InkWell(
    onTap: () {
      _incrementCount(null);
    },
    child: Text("count: $_count", style: commonTS()),
  ),
  MySetStateSecWidget(_count, _incrementCount),
],

class MySetStateSecWidget extends StatelessWidget {
  final int count;
  final ValueChanged incrementCount;

  const MySetStateSecWidget(this.count, this.incrementCount, {super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: InkWell(
        onTap: () {
          incrementCount(null);
        },
        child: Text("sec count: $count", style: commonTS()),
      ),
    );
  }
}

我们在Column中添加了一个MySetStateSecWidget,此Widget引用了MySetStateWidget_count值,并且在其内部的文本点击事件中回调给父Widget,父Widget接受到点击事件之后也是调用了自身的_count自增方法,这时候我们看到的画面就是无论点击哪个文本,都是可以改变_count的值,而且文本内容也随之改变。

看到此处大家是否在想,此时两个Widget处于同一个页面,只是在层级方面属于父子关系,这时候可以使用SetState方法来管理状态,如果MySetStateSecWidget是跳转之后的页面,此时还可以做到这种效果嘛?答案是否定的,_countMySetStateWidget内部的状态,可以传递给Sec,但是此时在Sec页面通过回调改变状态时,Sec的文本则不会随之而改变了,因为它不会重建了~

下面我们来接触下InheritedWidget,看看InheritedWidget是否能达到我们需要的效果呢?

InheritedWidget共享状态

InheritedWidget字面意思为继承的组件,它可以帮助我们在组件树中共享一些状态,下面我们还是以计数器的例子来使用InheritedWidget实现一次。

首先我们定义一个InheritedWidget的子类,内部包含一个count状态值,此状态值可以共享给子Widget。

scala 复制代码
class CountInheritedModel extends InheritedWidget {
  final int count;

  const CountInheritedModel(this.count, {required Widget child, Key? key}) : super(child: child, key: key);

  @override
  bool updateShouldNotify(covariant CountInheritedModel oldWidget) {
    return count != oldWidget.count;
  }

  static CountInheritedModel? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CountInheritedModel>();
  }
}

这里先留意一下静态方法of(context),暂时不考虑具体的内部实现,先知道它就是为了获取当前CountInheritedModel的一个静态方法而已,接下来我们会详细介绍内部的实现逻辑。

定义完InheritedWidget之后,我们再来实现计时器页面~

scala 复制代码
class CountInheritedWidget extends StatefulWidget {
  const CountInheritedWidget({super.key});

  @override
  State<StatefulWidget> createState() {
    return _CountInheritedState();
  }
}

class _CountInheritedState extends State<CountInheritedWidget> {
  int _count = 1;

  @override
  Widget build(BuildContext context) {
    return buildScaffold(
      context,
      CountInheritedModel(
        _count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text('Inherited Count: $_count'),
            const CountInheritedChildWidget(),
          ],
        ),
      ),
      actionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

class CountInheritedChildWidget extends StatelessWidget {
  const CountInheritedChildWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final count = CountInheritedModel.of(context)?.count ?? 0;
    return Center(
      child: Text("Child Count: $count"),
    );
  }
}

CountInheritedModel的child种包含了一个简单的文本,用于显示自身_count的值,另外一个则是CountInheritedChildWidget,此组件则是通过CountInheritedModel的静态方法of()来获取count值,下面我们来运行看看能达到一个什么样的效果。

通过上方的GIF可以看出,当我们点击FloatingActionButton的时候,两个Text显示的文本都会随之改变,这就是InheritedWidget给我们带来的共享状态之处,当我们在子Widget中需要共享状态时,无须像SetState那种传参进来,只需要通过CountInheritedModel的静态方法of()来获取共享值即可。

了解了InheritedWidget共享状态使用之后,接下来我们重点了解下of()这个方法内部的使用。

在上面的代码中我们采用的是context.dependOnInheritedWidgetOfExactType()方法来获取当前的InheritedWidget对象,其实还有另外一种方法也可以帮助我们获取到当前的InheritedWidget对象,就是context.getInheritedWidgetOfExactType()方法,两种方法都可以帮助我们在任何子组件中获取到共享组件对象,那么二者有何区别呢?

最后我们来分析下具体的实现原理,直接进入源码查看:

dart 复制代码
  @override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
    if (ancestor != null) {
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

  @override
  T? getInheritedWidgetOfExactType<T extends InheritedWidget>() {
    return getElementForInheritedWidgetOfExactType<T>()?.widget as T?;
  }

  @override
  InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement? ancestor = _inheritedElements == null ? null : _inheritedElements![T];
    return ancestor;
  }

仔细看二者的实现有共同之处,其实dependOnInheritedWidgetOfExactType()方法包含了getElementForInheritedWidgetOfExactType()逻辑,它们都是先从_inheritedElements对象中通过泛型值T来获取到对应的Element对象,此泛型T就是InheritedWidget类型,然后通过Element对象取出widget,只是dependOnInheritedWidgetOfExactType()方法比getInheritedWidgetOfExactType()多了一步dependOnInheritedElement()方法的调用,那我们只需要进入到此方法就可以得出他们的不同之处。

typescript 复制代码
  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget as InheritedWidget;
  }

  @protected
  void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
  }

  @protected
  void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
  }

其实dependOnInheritedElement内部的逻辑也是比较简单的,就是将Element存入到_dependencies对象中,_dependencies对象是一个Set集合,然后再调用ancestor对象的updateDependencies()方法将Element和aspect存到_dependents对象中,这个对象则是一个Map集合,它内部保存的都是与当前Element有依赖关系的组件,存入到其中的作用就是为了当状态改变时,可以直接通知所有有依赖关系的组件进行状态刷新。

看到这里大家是否觉得豁然开朗了,dependOnInheritedWidgetOfExactType()和getInheritedWidgetOfExactType()共同点就是为了获取共享InheritedWidget对象,而dependOnInheritedWidgetOfExactType()方法还会进行组件之间依赖关系的注册,那最后我们将计数器代码换成getInheritedWidgetOfExactType()方式看看效果。

通过具体的效果我们就可以很清楚的看出,通过getInheritedWidgetOfExactType()的方法子组件不会进行状态的实时更新,只会再初始的时候拿一次状态值,这就是getInheritedWidgetOfExactType()和dependOnInheritedWidgetOfExactType()最大的不同之处了。

写在最后

本篇文章主要介绍了Flutter中setState和InheritedWidget共享状态的基本使用,希望通过文章给阅读的小伙伴们带来一点帮助,后续会循序渐进逐步接触Flutter更多的知识。

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui