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

相关推荐
飞猿_SIR2 小时前
基于海思Hi3798MV200Android7.0聊聊HDMI色深模式和电视HDR
android·嵌入式硬件·音视频
come112342 小时前
ptyhon 基础语法学习(对比php)
android·学习
ClassOps2 小时前
Android 12 SplashScreen启动屏
android·kotlin
一直向钱3 小时前
android 增强版 RecyclerView
android
sun0077003 小时前
网络配置config.xml的android.mk解析
android·xml
Digitally4 小时前
如何将照片从Mac传输到安卓设备
android·macos
教程分享大师5 小时前
当贝安卓9.0_创维E900S_e910V10C_3798mv310处理器线刷烧录包可救砖带adb功能
android·adb
2501_916008896 小时前
iOS 不上架怎么安装?多种应用分发方式解析,ipa 文件安装、企业签名、Ad Hoc 与 TestFlight 实战经验
android·macos·ios·小程序·uni-app·cocoa·iphone
百锦再6 小时前
从 .NET 到 Java 的转型指南:详细学习路线与实践建议
android·java·前端·数据库·学习·.net·数据库架构