状态管理是声明式编程很是重要的一个概念,咱们在前面介绍过Flutter是声明式编程的,也区分声明式编程和命令式编程的区别。vue
这里,咱们就来系统的学习一下Flutter声明式编程中很是重要的状态管理git
不少从命令式编程框架(Android或iOS原生开发者)转成声明式编程(Flutter、Vue、React等)刚开始并不适应,由于须要一个新的角度来考虑APP的开发模式。github
Flutter做为一个现代的框架,是声明式编程的:web
在编写一个应用的过程当中,咱们有大量的State须要来进行管理,而正是对这些State的改变,来更新界面的刷新:算法
某些状态只须要在本身的Widget中使用便可编程
这种状态咱们只须要使用StatefulWidget对应的State类本身管理便可,Widget树中的其它部分并不须要访问这个状态。redux
这种方式在以前的学习中,咱们已经应用过很是屡次了。数据结构
开发中也有很是多的状态须要在多个部分进行共享app
这种状态咱们若是在Widget之间传递来、传递去,那么是无穷尽的,而且代码的耦合度会变得很是高,牵一发而动全身,不管是代码编写质量、后期维护、可扩展性都很是差。框架
这个时候咱们能够选择全局状态管理的方式,来对状态进行统一的管理和应用。
开发中,没有明确的规则去区分哪些状态是短时状态,哪些状态是应用状态。
可是咱们能够简单遵照下面这幅流程图的规则:
针对React使用setState仍是Redux中的Store来管理状态哪一个更好的问题,Redux的issue上,Redux的做者Dan Abramov,它这样回答的:
The rule of thumb is: Do whatever is less awkward
经验原则就是:选择可以减小麻烦的方式。
InheritedWidget和React中的context功能相似,能够实现跨组件数据的传递。
定义一个共享数据的InheritedWidget,须要继承自InheritedWidget
class HYDataWidget extends InheritedWidget {
final int counter; HYDataWidget({this.counter, Widget child}): super(child: child); static HYDataWidget of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } @override bool updateShouldNotify(HYDataWidget oldWidget) { return this.counter != oldWidget.counter; } } 复制代码
建立HYDataWidget,而且传入数据(这里点击按钮会修改数据,而且从新build)
class HYHomePage extends StatefulWidget {
@override _HYHomePageState createState() => _HYHomePageState(); } class _HYHomePageState extends State<HYHomePage> { int data = 100; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("InheritedWidget"), ), body: HYDataWidget( counter: data, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ HYShowData() ], ), ), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () { setState(() { data++; }); }, ), ); } } 复制代码
在某个Widget中使用共享的数据,而且监听
Provider是目前官方推荐的全局状态管理工具,由社区做者Remi Rousselet 和 Flutter Team共同编写。
使用以前,咱们须要先引入对它的依赖,截止这篇文章,Provider的最新版本为4.0.4:
dependencies:
provider: ^4.0.4 复制代码
在使用Provider的时候,咱们主要关心三个概念:
咱们先来完成一个简单的案例,将官方计数器案例使用Provider来实现:
第一步:建立本身的ChangeNotifier
咱们须要一个ChangeNotifier来保存咱们的状态,因此建立它
class CounterProvider extends ChangeNotifier {
int _counter = 100; int get counter { return _counter; } set counter(int value) { _counter = value; notifyListeners(); } } 复制代码
第二步:在Widget Tree中插入ChangeNotifierProvider
咱们须要在Widget Tree中插入ChangeNotifierProvider,以便Consumer能够获取到数据:
void main() {
runApp(ChangeNotifierProvider( create: (context) => CounterProvider(), child: MyApp(), )); } 复制代码
第三步:在首页中使用Consumer引入和修改状态
class HYHomePage extends StatelessWidget {
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("列表测试"), ), body: Center( child: Consumer<CounterProvider>( builder: (ctx, counterPro, child) { return Text("当前计数:${counterPro.counter}", style: TextStyle(fontSize: 20, color: Colors.red),); } ), ), floatingActionButton: Consumer<CounterProvider>( builder: (ctx, counterPro, child) { return FloatingActionButton( child: child, onPressed: () { counterPro.counter += 1; }, ); }, child: Icon(Icons.add), ), ); } } 复制代码
Consumer的builder方法解析:
步骤四:建立一个新的页面,在新的页面中修改数据
class SecondPage extends StatelessWidget {
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二个页面"), ), floatingActionButton: Consumer<CounterProvider>( builder: (ctx, counterPro, child) { return FloatingActionButton( child: child, onPressed: () { counterPro.counter += 1; }, ); }, child: Icon(Icons.add), ), ); } } 复制代码
事实上,由于Provider是基于InheritedWidget,因此咱们在使用ChangeNotifier中的数据时,咱们能够经过Provider.of的方式来使用,好比下面的代码:
Text("当前计数:${Provider.of<CounterProvider>(context).counter}",
style: TextStyle(fontSize: 30, color: Colors.purple), ), 复制代码
咱们会发现很明显上面的代码会更加简洁,那么开发中是否要选择上面这种方式了?
为何呢?由于Consumer在刷新整个Widget树时,会尽量少的rebuild Widget。
方式一:Provider.of的方式完整的代码:
class HYHomePage extends StatelessWidget {
@override Widget build(BuildContext context) { print("调用了HYHomePage的build方法"); return Scaffold( appBar: AppBar( title: Text("Provider"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text("当前计数:${Provider.of<CounterProvider>(context).counter}", style: TextStyle(fontSize: 30, color: Colors.purple), ) ], ), ), floatingActionButton: Consumer<CounterProvider>( builder: (ctx, counterPro, child) { return FloatingActionButton( child: child, onPressed: () { counterPro.counter += 1; }, ); }, child: Icon(Icons.add), ), ); } } 复制代码
方式二:将Text中的内容采用Consumer的方式修改以下:
Consumer<CounterProvider>(builder: (ctx, counterPro, child) {
print("调用Consumer的builder"); return Text( "当前计数:${counterPro.counter}", style: TextStyle(fontSize: 30, color: Colors.red), ); }), 复制代码
Consumer是不是最好的选择呢?并非,它也会存在弊端
咱们先直接实现代码,在解释其中的含义:
floatingActionButton: Selector<CounterProvider, CounterProvider>(
selector: (ctx, provider) => provider, shouldRebuild: (pre, next) => false, builder: (ctx, counterPro, child) { print("floatingActionButton展现的位置builder被调用"); return FloatingActionButton( child: child, onPressed: () { counterPro.counter += 1; }, ); }, child: Icon(Icons.add), ), 复制代码
Selector和Consumer对比,不一样之处主要是三个关键点:
这个时候,咱们从新测试点击floatingActionButton,floatingActionButton中的代码并不会进行rebuild操做。
因此在某些状况下,咱们可使用Selector来代替Consumer,性能会更高。
在开发中,咱们须要共享的数据确定不止一个,而且数据之间咱们须要组织到一块儿,因此一个Provider必然是不够的。
咱们在增长一个新的ChangeNotifier
import 'package:flutter/material.dart';
class UserInfo { String nickname; int level; UserInfo(this.nickname, this.level); } class UserProvider extends ChangeNotifier { UserInfo _userInfo = UserInfo("why", 18); set userInfo(UserInfo info) { _userInfo = info; notifyListeners(); } get userInfo { return _userInfo; } } 复制代码
若是在开发中咱们有多个Provider须要提供应该怎么作呢?
方式一:多个Provider之间嵌套
runApp(ChangeNotifierProvider( create: (context) => CounterProvider(), child: ChangeNotifierProvider( create: (context) => UserProvider(), child: MyApp() ), )); 复制代码
方式二:使用MultiProvider
runApp(MultiProvider( providers: [ ChangeNotifierProvider(create: (ctx) => CounterProvider()), ChangeNotifierProvider(create: (ctx) => UserProvider()), ], child: MyApp(), )); 复制代码
备注:全部内容首发于公众号,以后除了Flutter也会更新其余技术文章,TypeScript、React、Node、uniapp、mpvue、数据结构与算法等等,也会更新一些本身的学习心得等,欢迎你们关注