EN
/
中文
1. Three common State Management
Widget manages its own state.
Widget manages child Widget status.
Mixed management (both parent and child widgets manage state).
1.1.1 Applicable scenarios If the state is related to the appearance of the interface, such as color, animation, then the state is best managed by the Widget itself.
1.1.2 Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class TapboxA extends StatefulWidget { TapboxA({Key? key}) : super (key: key); @override _TapboxAState createState() => _TapboxAState(); } class _TapboxAState extends State <TapboxA > { bool _active = false ; void _handleTap() { setState(() { _active = !_active; }); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( _active ? 'Active' : 'Inactive' , style: TextStyle(fontSize: 32.0 , color: Colors.white), ), ), width: 200.0 , height: 200.0 , decoration: BoxDecoration( color: _active ? Colors.lightGreen[700 ] : Colors.grey[600 ], ), ), ); } }
1.2.1 Applicable scenarios If the state is user data, such as the selected state of a checkbox or the position of a slider, then the state is best managed by the parent Widget.
1.2.2 Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() => _ParentWidgetState(); } class _ParentWidgetState extends State <ParentWidget > { bool _active = false ; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } class TapboxB extends StatelessWidget { TapboxB({Key? key, this .active: false , required this .onChanged}) : super (key: key); final bool active; final ValueChanged<bool > onChanged; void _handleTap() { onChanged(!active); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( active ? 'Active' : 'Inactive' , style: TextStyle(fontSize: 32.0 , color: Colors.white), ), ), width: 200.0 , height: 200.0 , decoration: BoxDecoration( color: active ? Colors.lightGreen[700 ] : Colors.grey[600 ], ), ), ); } }
1.3.1 Applicable scenarios If a certain state is shared by different Widgets, it is best managed by their common parent Widget.
1.3.2 Example _ParentWidgetStateC Class:
Manage_active status.
Implements _handleTapboxChanged(), which is called when the box is clicked.
When the box is clicked and the _active status changes, setState() is called to update the UI.
_TapboxCState Object:
Manage_highlight status.
GestureDetector Listens for all tap events. When the user clicks, it adds a highlight (dark green border); when the user releases, it removes the highlight.
Updates the _highlight state when pressed, lifted, or canceled, and calls setState() to update the UI.
When clicked, pass state changes to the parent component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 class ParentWidgetC extends StatefulWidget { @override _ParentWidgetCState createState() => _ParentWidgetCState(); } class _ParentWidgetCState extends State <ParentWidgetC > { bool _active = false ; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxC( active: _active, onChanged: _handleTapboxChanged, ), ); } } class TapboxC extends StatefulWidget { TapboxC({Key? key, this .active: false , required this .onChanged}) : super (key: key); final bool active; final ValueChanged<bool > onChanged; @override _TapboxCState createState() => _TapboxCState(); } class _TapboxCState extends State <TapboxC > { bool _highlight = false ; void _handleTapDown(TapDownDetails details) { setState(() { _highlight = true ; }); } void _handleTapUp(TapUpDetails details) { setState(() { _highlight = false ; }); } void _handleTapCancel() { setState(() { _highlight = false ; }); } void _handleTap() { widget.onChanged(!widget.active); } @override Widget build(BuildContext context) { return GestureDetector( onTapDown: _handleTapDown, onTapUp: _handleTapUp, onTap: _handleTap, onTapCancel: _handleTapCancel, child: Container( child: Center( child: Text( widget.active ? 'Active' : 'Inactive' , style: TextStyle(fontSize: 32.0 , color: Colors.white), ), ), width: 200.0 , height: 200.0 , decoration: BoxDecoration( color: widget.active ? Colors.lightGreen[700 ] : Colors.grey[600 ], border: _highlight ? Border.all( color: Colors.teal[700 ], width: 10.0 , ) : null , ), ), ); } }
1.4 General selection rules The encapsulation of state management within the Widget will be better, while the management in the parent Widget will be more flexible. If you are not sure how to manage the state, the first recommended option is to manage it in the parent Widget (flexibility is more important).
2. Global State Management Practical example: We have a settings page where you can set the language of the application. In order to make the settings take effect in real time, we expect that the components in the App that depend on the application language can be rebuilt when the language status changes. However, these components that depend on the application language are not together with the settings page, so this situation is difficult to manage using the above method. At this time, the correct approach is to use a global State Manager to handle the communication between such distant components.
Implement a global event bus and represent language-state changes as events. In each app component that depends on the current language, subscribe to the language-change event in initState. When the user changes the language in Settings, publish the event. Subscribers then receive the notification and call setState(...) to rebuild themselves.
Use some packages specifically for State Management, such as Provider and Redux. Readers can view their details on pub.
一、三种常见的状态管理
Widget 管理自己的状态。
Widget 管理子 Widget 状态。
混合管理(父 Widget 和子 Widget 都管理状态)。
1.1.1 适用场景 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
1.1.2 例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class TapboxA extends StatefulWidget { TapboxA({Key? key}) : super (key: key); @override _TapboxAState createState() => _TapboxAState(); } class _TapboxAState extends State <TapboxA > { bool _active = false ; void _handleTap() { setState(() { _active = !_active; }); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( _active ? 'Active' : 'Inactive' , style: TextStyle(fontSize: 32.0 , color: Colors.white), ), ), width: 200.0 , height: 200.0 , decoration: BoxDecoration( color: _active ? Colors.lightGreen[700 ] : Colors.grey[600 ], ), ), ); } }
1.2.1 适用场景 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
1.2.2 例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() => _ParentWidgetState(); } class _ParentWidgetState extends State <ParentWidget > { bool _active = false ; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } class TapboxB extends StatelessWidget { TapboxB({Key? key, this .active: false , required this .onChanged}) : super (key: key); final bool active; final ValueChanged<bool > onChanged; void _handleTap() { onChanged(!active); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( active ? 'Active' : 'Inactive' , style: TextStyle(fontSize: 32.0 , color: Colors.white), ), ), width: 200.0 , height: 200.0 , decoration: BoxDecoration( color: active ? Colors.lightGreen[700 ] : Colors.grey[600 ], ), ), ); } }
1.3.1 适用场景 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。
1.3.2 例子 _ParentWidgetStateC类:
管理_active状态。
实现 _handleTapboxChanged() ,当盒子被点击时调用。
当点击盒子并且_active状态改变时调用setState()更新UI。
_TapboxCState对象:
管理_highlight状态。
GestureDetector监听所有tap事件。当用户点下时,它添加高亮(深绿色边框);当用户释放时,会移除高亮。
当按下、抬起、或者取消点击时更新_highlight状态,调用setState()更新UI。
当点击时,将状态的改变传递给父组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 class ParentWidgetC extends StatefulWidget { @override _ParentWidgetCState createState() => _ParentWidgetCState(); } class _ParentWidgetCState extends State <ParentWidgetC > { bool _active = false ; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxC( active: _active, onChanged: _handleTapboxChanged, ), ); } } class TapboxC extends StatefulWidget { TapboxC({Key? key, this .active: false , required this .onChanged}) : super (key: key); final bool active; final ValueChanged<bool > onChanged; @override _TapboxCState createState() => _TapboxCState(); } class _TapboxCState extends State <TapboxC > { bool _highlight = false ; void _handleTapDown(TapDownDetails details) { setState(() { _highlight = true ; }); } void _handleTapUp(TapUpDetails details) { setState(() { _highlight = false ; }); } void _handleTapCancel() { setState(() { _highlight = false ; }); } void _handleTap() { widget.onChanged(!widget.active); } @override Widget build(BuildContext context) { return GestureDetector( onTapDown: _handleTapDown, onTapUp: _handleTapUp, onTap: _handleTap, onTapCancel: _handleTapCancel, child: Container( child: Center( child: Text( widget.active ? 'Active' : 'Inactive' , style: TextStyle(fontSize: 32.0 , color: Colors.white), ), ), width: 200.0 , height: 200.0 , decoration: BoxDecoration( color: widget.active ? Colors.lightGreen[700 ] : Colors.grey[600 ], border: _highlight ? Border.all( color: Colors.teal[700 ], width: 10.0 , ) : null , ), ), ); } }
1.4 通用选型法则 在 Widget 内部管理状态封装性会好一些,而在父 Widget 中管理会比较灵活。 如果不确定到底该怎么管理状态,那么推荐的首选是在父 Widget 中管理(灵活会显得更重要一些)。
二、全局状态管理 实际例子:我们有一个设置页,里面可以设置应用的语言,我们为了让设置实时生效,我们期望在语言状态发生改变时,App中依赖应用语言的组件能够重新 build 一下,但这些依赖应用语言的组件和设置页并不在一起,所以这种情况用上面的方法很难管理。这时,正确的做法是通过一个全局状态管理器来处理这种相距较远的组件之间的通信。
实现一个全局的事件总线,将语言状态改变对应为一个事件,然后在APP中依赖应用语言的组件的initState 方法中订阅语言改变的事件。当用户在设置页切换语言后,我们发布语言改变事件,而订阅了此事件的组件就会收到通知,收到通知后调用setState(...)方法重新build一下自身即可。
使用一些专门用于状态管理的包,如 Provider、Redux,读者可以在 pub 上查看其详细信息。