手写一个Flutter State Widget,来让你彻底理解State的来龙去脉

2023-10-18 06:20

本文主要是介绍手写一个Flutter State Widget,来让你彻底理解State的来龙去脉,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

image

往期相关内容

  • Flutter State Management状态管理全面分析:https://www.jianshu.com/p/9334b8f68004
  • Flutter Provider 迄今为止最深、最全、最新的源码分析:https://juejin.im/post/6844904176074358791
  • Flutter之万物皆Widget:https://juejin.im/post/6873323337016311816

前言

在上期,我们手写了一个Widget的实现,并接触到了Element,经过一系列的分析,我们对Widget和Element的认识更进一步,那么这期我们就来深入理解下State,相信大家在开发过程中,总会用到StatefulWidget,那么官方为什么设计一个含有State的Widget?State生命周期是怎么来的?为什么State可以更新UI?带着一些疑问,我们不直接分析源码,而是手写一个带有State的Widget怎么样?我们来做一个带State的Widget,让它有生命周期和更新UI的能力。

本次主要内容

  • 宏观看state是什么,微观看State
  • State类继承关系图
  • 手写一个带State的Widget

宏观看state是什么,微观看State

image

从宏观来看,flutter的UI是声明式的,那为什么是声明式?这就要从Win32到Web再到Android和Ios说起,他们都是命令式的编程风格,如下:

//android
TextView tv = TextView()
tv.setText("text")

当UI发生变化的时候,你必须调用setText来实现,但flutter恰相反,它为了减轻开发人员的负担,让开发人员只关心当前应用的状态,并交给框架自动将状态通过函数渲染在UI上,那么这样做有什么好处呢?

  • 开发人员只关心状态的变化,从架构上做到了UI和数据的分离
  • 更深入的讲,其实flutter 真实的UI对象是RenderObjects,Widget是不变的,每次刷新UI都会构建新的子Widget树,并通过Element过滤,最终RenderObject只是很小的改动,提高了渲染的效率。

那么有什么缺点吗?

  • 不合理的状态管理,导致整个页面的频繁build
  • 在Widget树中加入了状态的计算,会导致状态管理的混乱,不统一

最理想的就是如上图的公式:UI= f(state) 举个例子:

class TestState extends StatefulWidget {@override_TestState createState() => _TestState();
}class _TestState extends State<TestState> {FunState _funState = FunState();@overrideWidget build(BuildContext context) {return Container(child: Column(children: [Text(_funState.state + "state"), /// 不推荐Text(_funState.getState()) /// 推荐写法,f(state) ],),);}
}class FunState {String state = "state";getState() {return state + "Test";}
}

看到了吧,我们不推荐你这样哦

Text(_funState.state + "state"), /// 不推荐

这就是我要说的宏观state,我们简单做个定义:state其实就是反应出当前UI的状态。那么微观呢?其实就是StatefulWidget的State,都知道每个StatefulWidget会对应一个State,上期我们也学习了Widget,了解到Widget实际上是通过Element来展示UI的,那么State到底是什么角色,有什么作用呢?或者说,为什么google要这么设计呢?让我们来慢慢揭晓答案,并最终总结一下。

State类继承关系图

image

像我常用的Form,FormField,Overlay,Scaffold Widget,它们都会对应一个自己的State,当然也有更深一层的继承关系如AnimatedWidgetBaseState,但它的子类都是私有的。以及其他的State,通过类的继承关系,大致了解到,State类不需要特别深入的继承关系,比Widget和Element都稍微简单一些,flutter在设计之初,就一致贯穿一个设计思想就是组合大于继承,所以这也是整个UI框架的特点,也是类图都很简单的主要原因。

手写一个带State的Widget

我们还是上期的套路,继承最底层的Widget来实现,这次加一个State,来伪装成StatefulWidget,来吧。

class StateWidget extends Widget{/// 构造函数const StateWidget({ Key key }) : super(key: key);@overrideElement createElement() {// TODO: implement createElementthrow UnimplementedError();}
}

创建StateWidget类,继承自Widget,它让我们实现一个Element,那我们就再创建一个Element,这次我们用ComponentElement,上期我们使用过Element了,实现起来较麻烦,这期我们要了解的是State对吧,所以我们继承ComponentElement来快速的实现并理解State

class StateWidget extends Widget{/// 构造函数const StateWidget({ Key key }) : super(key: key);@overrideElement createElement() {return StateElement(this);}
}class StateElement extends ComponentElement{StateElement(Widget widget) : super(widget);@overrideWidget build() {}
}

我们印象中State有很多属性和函数,都有那些呢?请看图

image

那我们就模仿一下,把这些函数定义一下,代码如下

abstract class States<T extends StateWidget> {T get widget => _widget;T _widget;BuildContext get context => _element;StateElement _element;@protected@mustCallSupervoid initState() {}@protected@mustCallSupervoid didUpdateWidget(covariant T oldWidget) {}@protected@mustCallSupervoid reassemble() {}@protected@mustCallSupervoid setState(VoidCallback fn) {}@protected@mustCallSupervoid deactivate() {}@protected@mustCallSupervoid dispose() {}@protectedWidget build(BuildContext context);@protected@mustCallSupervoid didChangeDependencies() { }}

好了我们的States就这样被定义完了,protected关键字跟java的作用域应该是一样的,mustCallSuper是让子类实现必须调用super.当前函数,当然它还有debugFillProperties等debug相关的函数,这些我们先不关心,我们先把最核心的问题搞定,接下来,如何将States接入Widget呢?想想我们之前怎么用的?

@override_TestState createState() => _TestState();

对的,Widget有个createState函数,我们也来加一下,如下:

abstract class StateWidget extends Widget {/// 构造函数const StateWidget({Key key}) : super(key: key);@overrideElement createElement() {return StateElement(this);}@protected@factoryStates createState();
}

factory的注释含义:
用于注释实例或静态方法。 表示该方法要么是抽象的,要么必须返回新分配的对象或“null”。另外,每个实现或覆盖该方法都是隐式的使用相同的注释进行注释。

通过实现,我们发现State其实同时有Widget和Element的引用的,Widget已经完成了,那再来看看Element如何做呢?我们再来看下Element的代码

class StateElement extends ComponentElement {///这里将之前的Widget改为StateWidget,免得强转StateElement(StateWidget widget) : super(widget);@overrideWidget build() {}
}

继承自ComponentElement,覆盖build函数,而State里面恰巧有个抽象函数build,那么肯定是这里了

class StateElement extends ComponentElement {States<StateWidget> get state => _state;States<StateWidget> _state;StateElement(StateWidget widget) : super(widget);@overrideWidget build() {return _state.build(this);}
}

在Element里缓存一下State,并在build中调用_state.build(this), 这个this就是我们熟悉的BuildContext,而BuildContext的实例就是当前Element对象。现在你会发现,_state并没有赋值对吧,它是widget里的createState函数返回的,那我们什么时候调用合适呢?为了避免它多次createState,在构造函数里是不是更合适呢?放进去如下

class StateElement extends ComponentElement {States<StateWidget> get state => _state;States<StateWidget> _state;StateElement(StateWidget widget)///创建State: _state = widget.createState(),super(widget){///断言判断assert(_state._element == null);///给State里的element赋值,也就是你在State里获取的context_state._element = this;assert(_state._widget == null);///state里的widget赋值_state._widget = widget;}@overrideWidget build() {return _state.build(this);}
}

在构造函数里已经将State里的element,widget统统赋值了,紧接着就是State的initState()函数,这是我们经常用的初始化函数,那么它是在Element什么时候被调用的呢?或者说什么时候调用比较合适,首先一点,它肯定只调用一次,不可能初始化两次把,这样不太合理,有人说放构造里行吗?我们再来看看Element的生命周期

image

系统调用createElement后,当Element真正被挂载到树中的时候,才会调用mount,如果Element根本没有挂载到UI上,我们是不是就没必要初始化呢?那放在构造合适吗?不合适对吧,所以实现如下:

class StateElement extends ComponentElement {States<StateWidget> get state => _state;States<StateWidget> _state;bool isNeedInit;StateElement(StateWidget widget)///创建State: _state = widget.createState(),super(widget){///断言判断assert(_state._element == null);///给State里的element赋值,也就是你在State里获取的context_state._element = this;assert(_state._widget == null);///state里的widget赋值_state._widget = widget;}@overridevoid mount(Element parent, newSlot) {super.mount(parent, newSlot);assert(isNeedInit == null);_state.initState();isNeedInit = false;}@overrideWidget build() {return _state.build(this);}
}

在mount函数中调用initState函数,并通过isNeedInit变量控制只调用一次。紧接着看didChangeDependencies函数,为什么说它呢?当此State对象的依赖项更改时调用,还有就是在initState后调用,官方解释:子类很少重写此方法,因为框架总是在依赖项更改后调用build。一些子类确实重写了此方法,因为当它们的依存关系发生变化时,它们需要做一些昂贵的工作(例如,网络获取),并且对于每个构建而言,所做的工作都太昂贵了。

详细理解请看大佬分析:https://www.jianshu.com/p/9cb6c57b796c

其实说白了就是Widget类型发生变化时,就会触发,State里的didChangeDependencies触发需要满足两个条件,一个就是第一次加载,它认为Widget类型从null转换为具体的Widget,再一个就是Element的Widget确实有了变化,系统调用Element的didChangeDependencies,这个时候才有必要执行State的didChangeDependencies,代码实现如下:

class StateElement extends ComponentElement {States<StateWidget> get state => _state;States<StateWidget> _state;bool isNeedInit;bool _didChangeDependencies = false;StateElement(StateWidget widget)///创建State: _state = widget.createState(),super(widget){///断言判断assert(_state._element == null);///给State里的element赋值,也就是你在State里获取的context_state._element = this;assert(_state._widget == null);///state里的widget赋值_state._widget = widget;}@overridevoid mount(Element parent, newSlot) {super.mount(parent, newSlot);assert(isNeedInit == null);_state.initState();///第一次加载的时候,Widget从Null变为具体的Widget_state.didChangeDependencies();isNeedInit = false;}@overridevoid didChangeDependencies() {super.didChangeDependencies();///Element的Widget确实有了变化_didChangeDependencies = true;}@overridevoid performRebuild() {///这个时候才有必要执行State的didChangeDependenciesif (_didChangeDependencies) {_state.didChangeDependencies();_didChangeDependencies = false;}super.performRebuild();}@overrideWidget build() {return _state.build(this);}
}

在element的performRebuild函数中执行State的didChangeDependencies,条件就是在系统执行了Element的didChangeDependencies函数。

再往下,我们看下reassemble函数,这个函数是干嘛的呢?此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。这个我们不必关心太多,直接在Element里调用即可,实现如下:

@overridevoid reassemble() {_state.reassemble();super.reassemble();}

再来看下didUpdateWidget函数,在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用,但你发现Element里面没有这个函数,它只有update函数。然后我看了下StatefulElement的实现就是在这里调用的didUpdateWidget,那我们来实现下,看看都什么逻辑

class StateElement extends ComponentElement {States<StateWidget> get state => _state;States<StateWidget> _state;bool isNeedInit;bool _didChangeDependencies = false;StateElement(StateWidget widget)///创建State: _state = widget.createState(),super(widget) {///断言判断assert(_state._element == null);///给State里的element赋值,也就是你在State里获取的context_state._element = this;assert(_state._widget == null);///state里的widget赋值_state._widget = widget;}@overridevoid mount(Element parent, newSlot) {super.mount(parent, newSlot);assert(isNeedInit == null);_state.initState();///第一次加载的时候,Widget从Null变为具体的Widget_state.didChangeDependencies();isNeedInit = false;}@overridevoid didChangeDependencies() {super.didChangeDependencies();///Element的Widget确实有了变化_didChangeDependencies = true;}@overridevoid performRebuild() {///这个时候才有必要执行State的didChangeDependenciesif (_didChangeDependencies) {_state.didChangeDependencies();_didChangeDependencies = false;}super.performRebuild();}@overridevoid reassemble() {_state.reassemble();super.reassemble();}@overridevoid update(Widget newWidget) {super.update(newWidget);assert(widget == newWidget);/// 先拿到旧的widgetfinal StateWidget oldWidget = _state._widget;/// 强制将状态至为可更新markNeedsBuild();/// 将State的Widget更新为新的Widget_state._widget = widget as StateWidget;/// 回调_state.didUpdateWidget_state.didUpdateWidget(oldWidget);/// 调用rebuild函数,最终调用performRebuild,更新Elementrebuild();}@overrideWidget build() {return _state.build(this);}
}

update函数如上实现,你会发现ditUpdateWidget参数是旧的Widget,这点使用的时候要注意哦。update函数结束了,来看下deactivate,当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。看实现很简单:

@overridevoid deactivate() {/// 不需要特殊处理_state.deactivate();super.deactivate();}

再来看下dispose函数,dispose意义上是释放,那Element是什么时候释放的呢?那肯定是unmount函数了,所以不言而喻,实现如下:

@overridevoid unmount() {super.unmount();_state.dispose();/// 至null来释放调引用_state._element = null;_state = null;}

压轴的函数setState来了,几乎State所有的生命周期函数里,没有几个是有实现的,而setState需要实现,它是Widget能够重建的核心,直接上代码分析哈:

abstract class States<T extends StateWidget> {T get widget => _widget;T _widget;BuildContext get context => _element;StateElement _element;@protected@mustCallSupervoid initState() {}@protected@mustCallSupervoid didUpdateWidget(covariant T oldWidget) {}@protected@mustCallSupervoid reassemble() {}@protected@mustCallSupervoid setState(VoidCallback fn) {assert(fn != null);///...省略了状态判断final dynamic result = fn() as dynamic;assert(() {if (result is Future) {throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('setState() callback argument returned a Future.'),ErrorDescription('The setState() method on $this was called with a closure or method that ''returned a Future. Maybe it is marked as "async".'),ErrorHint('Instead of performing asynchronous work inside a call to setState(), first ''execute the work (without updating the widget state), and then synchronously ''update the state inside a call to setState().'),]);}// We ignore other types of return values so that you can do things like://   setState(() => x = 3);return true;}());/// 最重要的一句,markNeedsBuild,让Element处与可更新状态,等下framework层主动刷新。_element.markNeedsBuild();}@protected@mustCallSupervoid deactivate() {}@protected@mustCallSupervoid dispose() {}@protectedWidget build(BuildContext context);@protected@mustCallSupervoid didChangeDependencies() {}
}

它首先判断了fn是否为空,然后加入了state状态的判断,这里我省略了,想看的可以直接看State源码哦。往下就是对fn函数的Future情况处理,最最后调用了_element.markNeedsBuild();对哦,这其实才是我们Widget重新构建的关键,它其实就是改了一个标志位_dirty,设置true后,framework层就知道它要更新,会执行响应的更新,由于实际的更新是异步的,所以你可以在setState函数的前后或者函数中,都可以更新状态。

好了终于实现完了,是骡子是马,总要拉出来溜溜,我们自己实现的States能用吗?来实验一发。代码如下:

class TestStateWidget extends StateWidget {@overrideStates<StateWidget> createState() {return TestStates();}
}class TestStates extends States<TestStateWidget> {String data;int num = 0;@overridevoid initState() {data = "123";super.initState();}@overrideWidget build(BuildContext context) {return Container(padding: EdgeInsets.all(8),child: Column(children: [Text(data),MaterialButton(onPressed: () {setState(() {data = "456${num++}";});},child: Text("更新"),)],),);}
}

放入main.dart中:

@overrideWidget build(BuildContext context) {// This method is rerun every time setState is called, for instance as done// by the _incrementCounter method above.//// The Flutter framework has been optimized to make rerunning build methods// fast, so that you can just rebuild anything that needs updating rather// than having to individually change instances of widgets.return Scaffold(appBar: AppBar(// Here we take the value from the MyHomePage object that was created by// the App.build method, and use it to set our appbar title.title: Text(widget.title),),body: Column(children: [// Center(//   // Center is a layout widget. It takes a single child and positions it//   // in the middle of the parent.//   child: isShow ? const TestWidget() : Container(),// ),// MaterialButton(//   onPressed: () {//     showialog(context);//   },//   child: Text('showDialog'),// ),// Center(//   // Center is a layout widget. It takes a single child and positions it//   // in the middle of the parent.//   child: isShow//       ? TestWidget(//           key: key,//         )//       : Container(//           padding: EdgeInsets.all(9),//           child: TestWidget(//             key: key,//           ),//         ),// ),TestStateWidget()],),floatingActionButton: FloatingActionButton(onPressed: () {setState(() {isShow = !isShow;});},tooltip: 'Increment',child: Icon(Icons.add),), // This trailing comma makes auto-formatting nicer for build methods.);}

运行效果:

image

点击更新

image

再点击

image

没毛病把,完成了,效果很好,实现了一套带State的Widget对吧。下面我们来个总结。

总结

生命周期总结一张图

image
  • State的状态都是来源于Element,说白了就是Element的中介,或者叫委托更合适
  • Context可以是Element,但State不能,因为他们不是继承关系。
  • 掌握Element的重要性又出来了,因为State的功能受限于Element。
  • 在MVVM架构中,State是不是充当着VM的角色?我觉得可以这么认为。

等等把,好了,讲到这里该告一段落了,期待你的认可,点个赞就行。感谢。


http://www.taodudu.cc/news/show-7987456.html

相关文章:

  • Swift - 学用 字典 Dictionary
  • Groovy闭包深入学习 - [203] 一直都有新高度 - ITeye技术网站
  • 学习Javascript闭包(Closure)
  • boost::transitive_closure用法的测试程序
  • cpu-z笔记本加条子
  • 不拒绝成长--个人提升与两年内规划
  • CODING与悬镜安全达成战略合作,引领DevOps向DevSecOps创新模式升级
  • NFTScan 与 Banksea Finance 在 NFT 源数据层面达成战略合作
  • NFTScan 与 0xScope 在 NFT 源数据层面达成战略合作
  • NFTScan 与 Atem Network 在 NFT 数据领域达成战略合作
  • The Sandbox 和 Chain Games 建立合作关系
  • NFTScan 与 NFTPlay 在 NFT 数据领域达成战略合作
  • 解决 U 盘无法识别,磁盘管理中格式化时提示 “系统找不到指定的文件”
  • 快速删除node_modules文件夹
  • Docker 创建 Redis、MySQL 容器
  • NPM镜像源查看和切换
  • 下拉选择树,可过滤搜索、单选及多选,基于 vue2 element-ui 封装
  • 4G已经来了,5G还会远吗
  • 5G网络开启物联网时代 抢占通信技术革命先机
  • 大学计算机软件转课程,基于STAD的大学计算机软件课程教学研究
  • 高中物理应用计算机教学心得,基于网络环境的高中物理教学模式的研究和实践...
  • 合作学习在计算机教学中应用,浅谈高职计算机教学中合作学习的应用及实践
  • 巧学活用html4,善用、活用、巧用、妙用
  • Vue实现下拉表格组件
  • layui 表格的下拉选择
  • 创建Excel表格下拉菜单
  • STM32flash读保护的分析及解决办法#STM32H750VBT6##ST-LINK-Utility##芯片被锁#
  • SPI Flash 读写
  • NAND Flash 读、写、擦除原理
  • STM32F1(Flash 读保护)
  • 这篇关于手写一个Flutter State Widget,来让你彻底理解State的来龙去脉的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



    http://www.chinasem.cn/article/230751

    相关文章

    认识、理解、分类——acm之搜索

    普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

    【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

    【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

    【C++高阶】C++类型转换全攻略:深入理解并高效应用

    📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

    Flutter 进阶:绘制加载动画

    绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

    深入理解RxJava:响应式编程的现代方式

    在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

    如何通俗理解注意力机制?

    1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

    深入理解数据库的 4NF:多值依赖与消除数据异常

    在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

    状态模式state

    学习笔记,原文链接 https://refactoringguru.cn/design-patterns/state 在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 在状态模式中,player.getState()获取的是player的当前状态,通常是一个实现了状态接口的对象。 onPlay()是状态模式中定义的一个方法,不同状态下(例如“正在播放”、“暂停

    分布式系统的个人理解小结

    分布式系统:分的微小服务,以小而独立的业务为单位,形成子系统。 然后分布式系统中需要有统一的调用,形成大的聚合服务。 同时,微服务群,需要有交流(通讯,注册中心,同步,异步),有管理(监控,调度)。 对外服务,需要有控制的对外开发,安全网关。

    Java IO 操作——个人理解

    之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字