【Flutter实战】状态管理

3.2 状态管理

响应式的编程框架中都会有一个永恒的主题——“状态(State)管理”,无论是在React/Vue(两者都是支持响应式编程的Web开发框架)还是Flutter中,他们讨论的问题和解决的思想都是一致的。所以,如果你对React/Vue的状态管理有了解,可以跳过本节。言归正传,我们想一个问题,StatefulWidget的状态应该被谁管理?Widget本身?父Widget?都会?还是另一个对象?答案是取决于实际情况!以下是管理状态的最常见的方法:

  • Widget管理自己的状态。
  • Widget管理子Widget状态。
  • 混合管理(父Widget和子Widget都管理状态)。

如何决定使用哪种管理方法?下面是官方给出的一些原则可以帮助你做决定:

  • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父Widget管理。
  • 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由Widget本身来管理。
  • 如果某一个状态是不同Widget共享的则最好由它们共同的父Widget管理。

在Widget内部管理状态封装性会好一些,而在父Widget中管理会比较灵活。有些时候,如果不确定到底该怎么管理状态,那么推荐的首选是在父widget中管理(灵活会显得更重要一些)。

接下来,我们将通过创建三个简单示例TapboxA、TapboxB和TapboxC来说明管理状态的不同方式。 这些例子功能是相似的 ——创建一个盒子,当点击它时,盒子背景会在绿色与灰色之间切换。状态 _active确定颜色:绿色为true ,灰色为false,如图3-4所示。a large grey box with the text, 'Inactive'

下面的例子将使用GestureDetector来识别点击事件,关于该GestureDetector的详细内容我们将在后面“事件处理”一章中介绍。

3.2.1 Widget管理自身状态

_TapboxAState 类:

  • 管理TapboxA的状态。
  • 定义_active:确定盒子的当前颜色的布尔值。
  • 定义_handleTap()函数,该函数在点击该盒子时更新_active,并调用setState()更新UI。
  • 实现widget的所有交互式行为。
1// TapboxA 管理自身状态. 2 3//------------------------- TapboxA ---------------------------------- 4 5class TapboxA extends StatefulWidget { 6 TapboxA({Key key}) : super(key: key); 7 8 9 _TapboxAState createState() => new _TapboxAState(); 10} 11 12class _TapboxAState extends State<TapboxA> { 13 bool _active = false; 14 15 void _handleTap() { 16 setState(() { 17 _active = !_active; 18 }); 19 } 20 21 Widget build(BuildContext context) { 22 return new GestureDetector( 23 onTap: _handleTap, 24 child: new Container( 25 child: new Center( 26 child: new Text( 27 _active ? 'Active' : 'Inactive', 28 style: new TextStyle(fontSize: 32.0, color: Colors.white), 29 ), 30 ), 31 width: 200.0, 32 height: 200.0, 33 decoration: new BoxDecoration( 34 color: _active ? Colors.lightGreen[700] : Colors.grey[600], 35 ), 36 ), 37 ); 38 } 39} 40

3.2.2 父Widget管理子Widget的状态

对于父Widget来说,管理状态并告诉其子Widget何时更新通常是比较好的方式。 例如,IconButton是一个图标按钮,但它是一个无状态的Widget,因为我们认为父Widget需要知道该按钮是否被点击来采取相应的处理。

在以下示例中,TapboxB通过回调将其状态导出到其父组件,状态由父组件管理,因此它的父组件为StatefulWidget。但是由于TapboxB不管理任何状态,所以TapboxBStatelessWidget

ParentWidgetState 类:

  • 为TapboxB 管理_active状态。
  • 实现_handleTapboxChanged(),当盒子被点击时调用的方法。
  • 当状态改变时,调用setState()更新UI。

TapboxB 类:

  • 继承StatelessWidget类,因为所有状态都由其父组件处理。
  • 当检测到点击时,它会通知父组件。
1// ParentWidget 为 TapboxB 管理状态. 2 3//------------------------ ParentWidget -------------------------------- 4 5class ParentWidget extends StatefulWidget { 6 7 _ParentWidgetState createState() => new _ParentWidgetState(); 8} 9 10class _ParentWidgetState extends State<ParentWidget> { 11 bool _active = false; 12 13 void _handleTapboxChanged(bool newValue) { 14 setState(() { 15 _active = newValue; 16 }); 17 } 18 19 20 Widget build(BuildContext context) { 21 return new Container( 22 child: new TapboxB( 23 active: _active, 24 onChanged: _handleTapboxChanged, 25 ), 26 ); 27 } 28} 29 30//------------------------- TapboxB ---------------------------------- 31 32class TapboxB extends StatelessWidget { 33 TapboxB({Key key, this.active: false, this.onChanged}) 34 : super(key: key); 35 36 final bool active; 37 final ValueChanged<bool> onChanged; 38 39 void _handleTap() { 40 onChanged(!active); 41 } 42 43 Widget build(BuildContext context) { 44 return new GestureDetector( 45 onTap: _handleTap, 46 child: new Container( 47 child: new Center( 48 child: new Text( 49 active ? 'Active' : 'Inactive', 50 style: new TextStyle(fontSize: 32.0, color: Colors.white), 51 ), 52 ), 53 width: 200.0, 54 height: 200.0, 55 decoration: new BoxDecoration( 56 color: active ? Colors.lightGreen[700] : Colors.grey[600], 57 ), 58 ), 59 ); 60 } 61}

3.2.3 混合状态管理

对于一些组件来说,混合管理的方式会非常有用。在这种情况下,组件自身管理一些内部状态,而父组件管理一些其他外部状态。

在下面TapboxC示例中,手指按下时,盒子的周围会出现一个深绿色的边框,抬起时,边框消失。点击完成后,盒子的颜色改变。 TapboxC将其_active状态导出到其父组件中,但在内部管理其_highlight状态。这个例子有两个状态对象_ParentWidgetState_TapboxCState

_ParentWidgetStateC 类:

  • 管理_active 状态。
  • 实现 _handleTapboxChanged() ,当盒子被点击时调用。
  • 当点击盒子并且_active状态改变时调用setState()更新UI。

_TapboxCState 对象:

  • 管理_highlight 状态。
  • GestureDetector监听所有tap事件。当用户点下时,它添加高亮(深绿色边框);当用户释放时,会移除高亮。
  • 当按下、抬起、或者取消点击时更新_highlight状态,调用setState()更新UI。
  • 当点击时,将状态的改变传递给父组件。
1//---------------------------- ParentWidget ---------------------------- 2 3class ParentWidgetC extends StatefulWidget { 4 5 _ParentWidgetCState createState() => new _ParentWidgetCState(); 6} 7 8class _ParentWidgetCState extends State<ParentWidgetC> { 9 bool _active = false; 10 11 void _handleTapboxChanged(bool newValue) { 12 setState(() { 13 _active = newValue; 14 }); 15 } 16 17 18 Widget build(BuildContext context) { 19 return new Container( 20 child: new TapboxC( 21 active: _active, 22 onChanged: _handleTapboxChanged, 23 ), 24 ); 25 } 26} 27 28//----------------------------- TapboxC ------------------------------ 29 30class TapboxC extends StatefulWidget { 31 TapboxC({Key key, this.active: false, this.onChanged}) 32 : super(key: key); 33 34 final bool active; 35 final ValueChanged<bool> onChanged; 36 37 38 _TapboxCState createState() => new _TapboxCState(); 39} 40 41class _TapboxCState extends State<TapboxC> { 42 bool _highlight = false; 43 44 void _handleTapDown(TapDownDetails details) { 45 setState(() { 46 _highlight = true; 47 }); 48 } 49 50 void _handleTapUp(TapUpDetails details) { 51 setState(() { 52 _highlight = false; 53 }); 54 } 55 56 void _handleTapCancel() { 57 setState(() { 58 _highlight = false; 59 }); 60 } 61 62 void _handleTap() { 63 widget.onChanged(!widget.active); 64 } 65 66 67 Widget build(BuildContext context) { 68 // 在按下时添加绿色边框,当抬起时,取消高亮 69 return new GestureDetector( 70 onTapDown: _handleTapDown, // 处理按下事件 71 onTapUp: _handleTapUp, // 处理抬起事件 72 onTap: _handleTap, 73 onTapCancel: _handleTapCancel, 74 child: new Container( 75 child: new Center( 76 child: new Text(widget.active ? 'Active' : 'Inactive', 77 style: new TextStyle(fontSize: 32.0, color: Colors.white)), 78 ), 79 width: 200.0, 80 height: 200.0, 81 decoration: new BoxDecoration( 82 color: widget.active ? Colors.lightGreen[700] : Colors.grey[600], 83 border: _highlight 84 ? new Border.all( 85 color: Colors.teal[700], 86 width: 10.0, 87 ) 88 : null, 89 ), 90 ), 91 ); 92 } 93}

另一种实现可能会将高亮状态导出到父组件,但同时保持_active状态为内部状态,但如果你要将该TapBox给其它人使用,可能没有什么意义。 开发人员只会关心该框是否处于Active状态,而不在乎高亮显示是如何管理的,所以应该让TapBox内部处理这些细节。

3.2.4 全局状态管理

当应用中需要一些跨组件(包括跨路由)的状态需要同步时,上面介绍的方法便很难胜任了。比如,我们有一个设置页,里面可以设置应用的语言,我们为了让设置实时生效,我们期望在语言状态发生改变时,APP中依赖应用语言的组件能够重新build一下,但这些依赖应用语言的组件和设置页并不在一起,所以这种情况用上面的方法很难管理。这时,正确的做法是通过一个全局状态管理器来处理这种相距较远的组件之间的通信。目前主要有两种办法:

  1. 实现一个全局的事件总线,将语言状态改变对应为一个事件,然后在APP中依赖应用语言的组件的initState 方法中订阅语言改变的事件。当用户在设置页切换语言后,我们发布语言改变事件,而订阅了此事件的组件就会收到通知,收到通知后调用setState(...)方法重新build一下自身即可。
  2. 使用一些专门用于状态管理的包,如Provider、Redux,读者可以在pub上查看其详细信息。

本书将在"功能型组件"一章中介绍Provider包的实现原理及用法,同时也将会在"事件处理与通知"一章中实现一个全局事件总线,读者有需要可以直接翻看。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

React Hook丨用好这9个钩子,所向披靡

ReactHook指南什么是Hook?Hook是React16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性。Hook本质上就是一个函数,它简洁了组件,有自己的状态管理,生命周期管理,状态共享。useStateuseEffectuseContextus

了解Vuex状态管理模式

1Vuex是什么呢?它是Vue的状态管理模式,在使用vue的时候,需要在vue中各个组件之间传递值是很痛苦的,在vue中我们可以使用vuex来保存我们需要管理的状态值,值一旦被改变,所有引用该值的地方就会自动更新。是不是很方便,很好用呢?vuex是专门为vue.js设计的状态管理模式,集中式存储和管理应用程序中所有组件的状态,vuex也集成了vue的

2021前端技术面试必备Vue:(四)Vuex状态管理

前三章陆续已经更新了Vue基本使用和VueRouter的基本使用,如果你读了前三篇文章的话,并且掌握差不多,那么你现在可以开发简单的应用了,例如Blog,电商网站........唯一不足的是,随着你的业务需求不断增加,页面中的状态数据也不断庞大,维护起来就特别困难了,Vue提供了一种状态管理解决办法Vuex,它的思想和React的R

mysql设置时区

mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0