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,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~

相关推荐
不爱学习的啊Biao1 分钟前
【13】MySQL如何选择合适的索引?
android·数据库·mysql
Clockwiseee29 分钟前
PHP伪协议总结
android·开发语言·php
mmsx7 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
众拾达人10 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌11 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
_Shirley12 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
hedalei14 小时前
RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题
android·android14·rk3576
锋风Fengfeng14 小时前
安卓多渠道apk配置不同签名
android
枫_feng14 小时前
AOSP开发环境配置
android·安卓
叶羽西15 小时前
Android Studio打开一个外部的Android app程序
android·ide·android studio