Flutter 的状态管理

状态提升(Lifting-state-up)

把子组件的状态,提升到上级组件中,从而实现在多个组件之间共享和同步数据的效果

以 flutter counter demo,那个按按钮+1 的来说,现在的 count 是几,不是存在页面显示几的地方,而是作为 HomePage 的一个 state,这样就提到了上级;子组件那个按钮的 press 事件,也不是说找到页面显示几的 Text 元素,然后改那个元素,而是改 state

子组件获取和控制父组件的状态

父组件传给子组件,只需要直接传参数进去即可

class Child extends StatelessWidget {final int stateFromParent;const Child({Key? key, required this.stateFromParent}): super(key: key);
}

子组件想要修改父组件的变量,需要使用 callback。在 dart 中,函数也可以作为参数传递,

class Child extends StatelessWidget {final void Function() changeStateInParent;const Child({Key? key, required this.changeStateInParent}): super(key: key);Widget build(BuildContext context) {return ElevatedButton(onPressed: changeStateInParent);}
}

关于 Funtion 类型

final void Function() changeStateInParent;

void 是代表这个函数的返回值
()是代表这个函数没有参数

为什么是这个顺序呢,因为我们平时写函数的时候也是这样

int fun(int x, int y){...
}

那么这个 fun 函数的函数类型就是 int Function(int x, int y)

void Function()可以缩写为 VoidCallback,因为 flutter 里面有如下定义

typedef VoidCallback = void Function();

关于 BuildContext

BuildContext 在 flutter 中是用于定位当前 Widget 在 Widget 树中位置的对象,用于访问父 widget 和其他相关信息,在构建 UI 时调用

比如,用 Nevigator 进行页面导航的时候,需要使用 BuildContext 来获取当前 Scaffold(页面基本元素布局,如 appBar 之类的)或 MaterialApp,以执行页面跳转操作

BuildContext 还可用于查找和访问在 widget 树中的其他 widget

onPressed: () {Scaffold scaffold = Scaffold.of(context);scaffold.showSnackBar(SnackBar(content: Text('Button Pressed')))
}

控制器(父组件控制子组件的状态)

第一个思路,状态提升,将子组件的状态提升到父组件上,可以,但是有一些问题

  1. 子组件不是我们自己写的,而是用的别人的库,这样就没办法要求它提升到我们的父组件了
  2. 而且我们封装子组件的目的就是为了提高性能,结果提升到父组件了,又要整体进行重绘,如拆
  3. 如果这个状态是基础数据类型,那么父组件给子组件传递的是值,是一个副本,子组件去修改这个值的时候,修改不到父组件的版本

解决状态提升的基础数据类型问题

基础数据类型下,父组件给子组件传递的是值不是引用,父组件控制子组件的功能是好的,但是如果子组件想正常的管理自己的 state,就通知不到父组件

解决方法是将 state 从基础数据类型转换为一个复杂的结构,这样传的就是引用,而不是值了

class IntHolder {int value;IntHolder(this.value);
}class _ParentState extends State<Parent> {IntHolder ih = IntHolder(1);Widget build(BuildContext context) {return Child(ih);}
}class Child extends StatefulWidget {final IntHolder ih;const Child({Key? key, required this.ih});
}class _ChildState extends State<Child> {Widget build(BuildContext context){return Column(children: [Text(widget.ih.value);ElevatedButton(onPressed: () {setState(() {widget.ih.value = 2; });});]);}
}

解决整体重绘的问题

子组件可以监听一个 stream,当 stream 发生变化的时候,这个子组件也可以发生变化,这个解决方案有点太“重”了,我们这个假如就是传递一个数,结果整了一个 stream,没必要

如果父组件有这么一个功能,当某个 state 变化的时候,能够“通知”相对应的子组件让它变化,这个过程中父组件自身不变,就可以完美解决这个问题了

这就要求 state 不是直接以 state 形式被提升在父组件里,而是被封装起来。即使里面的值发生变化,从父组件的角度看,这个被封装起来的块没有发生变化,就不会引发父组件重绘;同时,里面的值发生变化后,又要能够通知子组件,让子组件进行重绘

flutter 有一个 ChangeNotifier 类,可以有这样的效果,当 object 更新时,通知子组件更新

下面的例子是官方文档举的例子,其中 CounterModel 就混入了 ChangeNotifier,当_count 变化时,会通知这个 notifier 所在的 ListenableBuilder,然后 ListenableBuilder 重新使用 builder 进行 build

// 这里的 with 相当于 mixin,直接混入进去,如 class Dove extends Bird with Walker, Flyer {}
class CounterModel with ChangeNotifier {int _count = 0;int get count => _count;void increment() {_count += 1;notifyListeners(); // 在 _count 变化后通知}// 下面还能再加一些其他的更新 _count 的逻辑
}class ListenableBuilderExample extends StatefulWidget {const ListenableBuilderExample({super.key});State<ListenableBuilderExample> createState() =>_ListenableBuilderExampleState();
}class _ListenableBuilderExampleState extends State<ListenableBuilderExample> {final CounterModel _counter = CounterModel();Widget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('ListenableBuilder Example')),body: CounterBody(counterNotifier: _counter),floatingActionButton: FloatingActionButton(onPressed: _counter.increment,child: const Icon(Icons.add),),),);}b
}class CounterBody extends StatelessWidget {const CounterBody({super.key, required this.counterNotifier});final CounterModel counterNotifier;Widget build(BuildContext context) {return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text('Current counter value:'),// Thanks to the ListenableBuilder, only the widget displaying the// current count is rebuilt when counterValueNotifier notifies its// listeners. The Text widget above and CounterBody itself aren't// rebuilt.ListenableBuilder(listenable: counterNotifier, // 每当 counterNotifier 里 notifyListeners 后builder: (BuildContext context, Widget? child) { // 重新 buildreturn Text('${counterNotifier.count}');},),],),);}
}

这个 CounterModel 就是一个控制器,可以直接把它重新命名为 CounterController


如果是例子中这样,只为了一个变量实现一个类的 ChangeNotifier,有点繁琐。Flutter 为这种单变量值发生变化的 Notifier 提供了一个专门的类,ValueNotifier。CounterController 可以直接写成:

class CounterController {ValueNotifier count = ValueNotifier(0); // ValueNotifier 本身继承了 ChangeNotifier
}

注意,这样修改之后,count 变成了一个 Notifier,所以使用 count 值的时候,要写成 count.value

        ListenableBuilder(listenable: widget.controller.count, // 每当 ValueNotifier count 的value 变化builder: (BuildContext context, Widget? child) { // 重新 buildreturn Text('${widget.controller.count.value}');},),

如果同时需要监听多个 Notifier 的变化,使用 Listenable.merge

        ListenableBuilder(listenable: Listenable.merge([widget.controller.count,widget.controller.fontSize])builder: (BuildContext context, Widget? child) { // 重新 buildreturn Text('${widget.controller.count.value}',style: TextStyle(fontSize: widget.controller.fontSize.value),);},),

如果是这样多个 Notifier,可以统一放在一起,提升到父组件中,这就是控制器类(Controller)

组件在开发的时候,一般都遵循这个规范,使用 Controller。所以我们用别人写的组件的时候,只需要用他们写好的 Controller 就能实现父组件控制子组件的状态,且不影响子组件自己控制自己状态了

继承式组件 InheritedWidget

如果 state 被提升到顶部,就要一层一层传,这样,每一层组件的构造函数都有大量的参数

InheritedWidget 可以解决这一问题

class MyColor extends InteritedWidget {final Color color;MyColor({super.key, required super.child, required this.color});
}// 在组件树较高的位置用 MyColor 包裹
// 这样里面的所有组件都能访问到 color 这个属性
class MyApp extends StatelessWidget {const MyApp({Key? key}): super(key: key);Widget build(BuildContext context) {return MyColor(child: MaterialApp(....),color: Color.red);}
}

dependOnInheritedWidgetOfExactType:依赖于 继承式组件 of 特定的 Type
因为在某个组件所在的那一支上可能有其他的继承式组件,我们要找那个特定的继承式组件(这里 MyColor)

具体的寻找方法,就是从这个组件向上,向组件树的根部去找,直到找到 ExactType
如果有多个同样的组件(MyColor),选最近的那一个

组件如何访问到这个继承式组件的属性呢

Widget build(BuildContext context) {final myColor = context.dependOnInheritedWidgetOfExactType<MyColor>();return Container(color: myColor.color,...);
}

关于 updateShouldNotify

InteritedWidget 有一个函数 updateShouldNotify,是指当 InheritedWidget 发生变化的时候,需不需要通知相关的子组件进行重绘

按理说,变了肯定要通知,要不然不白变了吗

但是有的时候,InteritedWidget 的属性是被 setState 变掉的,setState 本身就会让子组件刷新,所以不用通知,子组件本来就是新的

class MyColor extends InteritedWidget {final Color color;// color 是父组件传进来的,是父组件的 state。父组件 setState 后子组件也重绘了MyColor({super.key, required super.child, required this.color});bool updateShouldNotify(covariant Inherited oldWidget) {// return true;return false; // 颜色也会变化,不过不是 updateShouldNotify 导致的}
}

虽然能够成功更新颜色,但这种一 setState,全局组件就要重绘,不是我们想看到的,所以要多加 const

return const Child();

这样的话父组件里的 state 发生变化,子组件就不会自动重绘了
这样 updateShouldNotify 的重要性就凸显出来了


bool updateShouldNotify(covariant Inherited oldWidget) {return color != oldWidget.color;
}

当新的 color 不等于旧的 color 时,告诉子组件刷新


如果是希望获取一次 color 后就不管了,不再监听之后 color 的更新,可以使用 getInheritedWidgetOfExactType

    final myColor = context.getInheritedWidgetOfExactType<MyColor>();

在 color 更新后,如果 updateShouldNotify 为 true,且子组件是 dependOnInheritedWidgetOfExactType,也会调用 didChangeDependencies 这个生命周期函数

关于 of

如果能将 dependOnInheritedWidgetOfExactType 封装起来,不用每次子组件使用的时候都写这么长一串,那就更好了。直接将这个封装到 MyColor 这个继承式组件中

有两种,of 和 maybeOf
of 指一定能从这个 context (组件树往上找)中找到 MyColor
maybeOf 指有可能能找到,可能为空

class MyColor extends InteritedWidget {final Color color;MyColor({super.key, required super.child, required this.color});static MyColor of(BuildContext context) {return context.dependOnInheritedWidgetOfExactType<MyColor>()!;// !代表确信不为空}// 或static MyColor maybeOf(BuildContext context) {return context.dependOnInheritedWidgetOfExactType<MyColor>();}bool updateShouldNotify(covariant Inherited oldWidget) {return color != oldWidget.color;}
}

通过这种写法,子组件找到继承式组件就能更加简洁了

color: MyColor.of(context).color;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/713812.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

政府采购标书制作的要点解析

导语&#xff1a;政府采购是政府为满足公共利益&#xff0c;按照法定程序和标准&#xff0c;通过招标、竞争性谈判等方式&#xff0c;购买商品、工程和服务的行为。标书作为政府采购活动中的重要文件&#xff0c;其制作质量直接影响到项目的顺利进行。本文将围绕政府采购标书制…

二路归并排序的算法设计和复杂度分析and周记

数据结构实验报告 实验目的: 通过本次实验&#xff0c;了解算法复杂度的分析方法&#xff0c;掌握递归算法时间复杂度的递推计算过程。 实验内容&#xff1a; 二路归并排序的算法设计和复杂度分析 实验过程&#xff1a; 1.算法设计 第一步&#xff0c;首先要将数组进行…

【网站项目】314学生二手书籍交易平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

关于游戏公司组织架构的小讨论

过完年刚刚上班没几天&#xff0c;就有一件比较搞笑的事情&#xff0c;可以和大家分享一下。   某一天我们在公司的会议室开会&#xff0c;发现有非常多蚊子&#xff0c;于是找行政问能不能找专业人士来灭蚊。行政的答复是&#xff0c;专业灭蚊是有固定时间的&#xff0c;还要…

JVM相关面试题(2024大厂高频面试题系列)

一、JVM的组成 1、JVM由哪些部分组成&#xff0c;运行流程是什么&#xff1f; 回答&#xff1a;在JVM中共有四大部分&#xff0c;分别是Class Loader&#xff08;类加载器&#xff09;、Runtime Data Area&#xff08;运行时数据区&#xff0c;内存分区&#xff09;、Execut…

MyBatis的补充用法

说明&#xff1a;之前介绍过MyBatis的用法&#xff0c;像 用注解和Mapper.xml操作数据库、在Mapper.xml里写动态SQL。最近在一次用MyBatis批量更新数据库对象的场景中&#xff0c;意识到对MyBatis的一些标签用法不太熟悉&#xff0c;所以去 MyBatis官网 看了一些文档&#xff0…

php httpfs链接hdfs

一.代码&#xff08;有bug&#xff09; GitHub - michaelbutler/php-WebHDFS: A PHP client for WebHDFS 二.调用代码 1.代码1.代码 require_once(../webhdfs/src/org/apache/hadoop/WebHDFS.php);require_once(../webhdfs/src/org/apache/hadoop/tools/Curl.php); require_o…

什么是人才储备?如何做人才储备?

很多小伙伴都会有企业面试被拒的情况&#xff0c;然后HR会告诉你&#xff0c;虽然没有录用你&#xff0c;但是你进入了他们的人才储备库&#xff0c;那么这个储备库有什么作用和特点呢&#xff1f;我们如何应用人才测评系统完善人才储备库呢&#xff1f; 人才储备一般有以下三…

Python打发无聊时光:12.用PyQt实现简易的心电起搏器界面

第一步&#xff1a;装PyQt库 pip install PyQt5 第二步&#xff1a;复制代码 import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QVBoxLayout,QWidget, QLabel, QProgressBar, QSlider, QLineEdit, QHBoxLayout) from PyQt5.QtCore import …

软件分层(数据结构/软件逻辑上分层+举例),相连节点的概念+如何相连,为什么是层状结构(软件分层,网络协议分层+梳理协议顺序),协议分层(打电话例子)

目录 软件分层 介绍 举例 类的继承 虚拟文件系统 线程接口封装 虚拟地址空间 总结 为什么是层状的 软件分层 网络协议 原因 梳理协议顺序 相连节点 协议分层 引入 示例 实际上 逻辑上 制定出协议 软件分层 介绍 通过将软件系统划分为不同的层次,每一层都有…

uniApp 调整小程序 单个/全部界面横屏展示效果

我们打开uni项目 小程序端运行 默认是竖着的一个效果 我们打开项目的 pages.json 给需要横屏的界面 的 style 属性 加上 "mp-weixin": {"pageOrientation": "landscape" }界面就横屏了 如果是要所有界面都横屏的话 就直接在pages.json 的 gl…

Ps:海绵工具

海绵工具 Sponge Tool可用于调整图像中特定区域的饱和度&#xff0c;常用于增加或减少颜色的饱和度。 快捷键&#xff1a;O 在特别的灰度图像上&#xff0c;则可用于调整对比度&#xff0c;这可以开发出更多的创意技巧。 ◆ ◆ ◆ 常用操作方法与技巧 1、海绵工具主要用于调整…

源码解析篇 | YOLOv8官方源码项目目录结构解析

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。YOLOv8是一种目标检测算法&#xff0c;它是YOLO&#xff08;You Only Look Once&#xff09;系列算法的第8个版本。YOLOv8相比于之前的版本&#xff0c;在检测精度和速度上都有所提升&#xff0c;它在各种场景下都表现出色…

Git源码管理

参考视频&#xff1a;16-git的日志以及版本管理_哔哩哔哩_bilibili 参考博客&#xff1a;Git && Docker 学习笔记-CSDN博客 目录 简介 个人操作初始化 初始化git目录 查看生成的git目录文件 配置git工作目录的用户信息 查看工作区的状态&#xff0c;生成文件的…

【JS】生成N位随机数

作用 用于邮箱验证码 码 ramNum.js /*** 生成N位随机数字* param {Number} l 默认&#xff1a;6&#xff0c;默认生成6位随机数字* returns 返回N位随机数字*/ const ramNum (l 6) > {let num for (let i 0; i < l; i) {const n Math.random()const str String(n…

C++面试干货---带你梳理常考的面试题(一)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 1.C和C的区别 1.语法和特性&#xff1a;C是一种过程式编程语言&#xff0c;而C是一种面向对象编程语言。C在C的基础上增加…

Java智慧云HIS医院信息化系统源码 更具灵活性、扩展性

目录 什么是云HIS 趋势与转变 HIS上云后有哪些好处 解决方案 云HIS组成 1、门诊挂号 2、住院管理 3、电子病历 4、药物管理 5、统计报表 6、综合维护 7、运营运维 什么是云HIS 云HIS是一种基于云计算技术的医院信息管理系统。云HIS可以帮助医院管理各类医院信息&a…

CIE-Alevel-Physics分类真题下载(更新中)

链接真题归类年份https://www.savemyexams.com/https://gceguide.com/papershttps://pastpapers.papacambridge.com/https://rocketrevise.comhttps://www.exam-mate.com/markhint.inhttps://xtremepape.rs/threads/as-and-a-level-physics-topical-pastpapers-upto-2015-with-…

Java Linux基本命令面试题

Java Linux基本命令面试题 前言1、查看文件内容有哪些命令可以使用&#xff1f;2、终端是哪个文件夹下的哪个文件&#xff1f;黑洞文件是哪个文件夹下的哪个命令&#xff1f;3、用什么命令对一个文件的内容进行统计&#xff1f;(行号、单词数、字节数)4、怎么使一个命令在后台运…

每日OJ题_分治归并②_力扣LCR 170. 交易逆序对的总数

目录 力扣LCR 170. 交易逆序对的总数 解析代码1 解析代码2 力扣LCR 170. 交易逆序对的总数 LCR 170. 交易逆序对的总数 难度 困难 在股票交易中&#xff0c;如果前一天的股价高于后一天的股价&#xff0c;则可以认为存在一个「交易逆序对」。请设计一个程序&#xff0c;输…