前言
在Flutter开发中,状态管理是一个永恒的话题。 通常的原则是:若是状态是组件私有的,则应该由组件本身管理;若是状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。 对于组件私有的状态管理很好理解,但对于跨组件共享的状态,管理的方式就比较多了,如使用全局事件总线EventBus,它是一个观察者模式的实现,经过它就能够实现跨组件状态同步:状态持有方(发布者)负责更新、发布状态,状态使用方(观察者)监听状态改变事件来执行一些操做。 可是观察者模式来实现跨组件状态共享有一些明显的缺点:redux
- 必须显示定义各类事件,很差管理;
- 订阅者必须显式注册状态改变回调,也必须在组件销毁时手动去解绑回调以免内存泄漏;
是否还有更好的跨组件状态管理方式了? 咱们知道,InheritedWidget能绑定与它依赖的子孙组件的依赖关系,而且当InheritedWidget数据发生变化时,能够自动更新依赖的子孙组件。基于此,能够将须要跨组件共享的状态保存在InheritedWidget中,而后在子组件中引用InheritedWidget便可。Flutter社区的Provider包就是基于这个思想实现的。缓存
Provider
基于上面的思想,实现一个最小功能的Provider。app
定义一个保存共享数据的类
为了通用性,使用泛型。less
class InheritedProvider<T> extends InheritedWidget{ InheritedProvider({@required this.data, Widget child}): super(child: child); // 共享状态使用泛型 final T data; bool updateShouldNotify(InheritedProvider<T> old){ // 返回true,则每次更新都会调用依赖其的子孙节点的didChangeDependencies return true; } }
数据发生变化时如何重构InheritedProvider
存在两个问题:ide
- 数据发生变化怎么通知?
- 谁来从新构建InheritedProvider?
对于第一个问题,可使用以前介绍的eventBus来进行事件通知,可是为了更贴近Flutter开发,使用Flutter中SDK中提供的ChangeNotifier类 ,它继承自Listenable,也实现了一个Flutter风格的发布者-订阅者模式,能够经过调用addListener()和removeListener()来添加、移除监听器(订阅者);经过调用notifyListeners() 能够触发全部监听器回调。 对于第二个问题,将要共享的状态放到一个Model类中,而后让它继承自ChangeNotifier,这样当共享的状态改变时,咱们只须要调用notifyListeners() 来通知订阅者,而后由订阅者来从新构建InheritedProvider。优化
// 该方法用于Dart获取模板类型 Type _typeOf<T>() => T; class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget{ ChangeNotifierProvider({ Key key, this.data, this.child, }); final Widget child; final T data; // 定义一个便捷方法,方便子树中的widget获取共享数据 // static T of<T>(BuildContext context){ // final type = _typeOf<InheritedProvider<T>>(); // final provider = context.inheritFromWidgetOfExactType(type) as InheritedProvider<T>; // return provider.data; // } // 替换上面的便捷方法,按需求是否注册依赖关系 static T of<T>(BuildContext context, {bool listen = true}){ final type = _typeOf<InheritedProvider<T>>(); final provider = listen ? context.inheritFromWidgetOfExactType(type) as InheritedProvider<T> : context.ancestorInheritedElementForWidgetOfExactType(type)?.widget as InheritedProvider<T>; return provider.data; } _ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>(); } // _ChangeNotifierProviderState类的主要做用就是监听到共享状态(model)改变时从新构建Widget树。 // 注意,在_ChangeNotifierProviderState类中调用setState()方法,widget.child始终是同一个, // 因此执行build时,InheritedProvider的child引用的始终是同一个子widget, // 因此widget.child并不会从新build,这也就至关于对child进行了缓存!固然若是ChangeNotifierProvider父级Widget从新build时,则其传入的child便有可能会发生变化。 class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>>{ void update(){ // 若是数据发生变化(model类调用了notifyListeners),从新构建InheritedProvider setState(() => {}); } @override void didUpdateWidget(ChangeNotifierProvider<T> oldWidget){ // 当Provider更新时,若是新旧数据不相等,则解绑旧数据监听,同时添加新数据监听 if(widget.data != oldWidget.data){ oldWidget.data.removeListener(update); widget.data.addListener(update); } super.didUpdateWidget(oldWidget); } @override void initState(){ // 给model添加监听器 widget.data.addListener(update); super.initState(); } @override void dispose(){ // 移除model的监听器 widget.data.removeListener(update); super.dispose(); } @override Widget build(BuildContext context){ return InheritedProvider<T>( data: widget.data, child: widget.child, ); } }
能够看到_ChangeNotifierProviderState类的主要做用就是监听到共享状态(model)改变时从新构建Widget树。注意,在_ChangeNotifierProviderState类中调用setState()方法,widget.child始终是同一个,因此执行build时,InheritedProvider的child引用的始终是同一个子widget,因此widget.child并不会从新build,这也就至关于对child进行了缓存!固然若是ChangeNotifierProvider父级Widget从新build时,则其传入的child便有可能会发生变化。ui
代码示例
// 跨组件状态共享(Provider) // 一个通用的InheritedWidget,保存任须要跨组件共享的状态 import 'dart:collection'; import 'package:flutter/material.dart'; class InheritedProvider<T> extends InheritedWidget{ InheritedProvider({@required this.data, Widget child}): super(child: child); // 共享状态使用泛型 final T data; bool updateShouldNotify(InheritedProvider<T> old){ // 返回true,则每次更新都会调用依赖其的子孙节点的didChangeDependencies return true; } } // 该方法用于Dart获取模板类型 Type _typeOf<T>() => T; class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget{ ChangeNotifierProvider({ Key key, this.data, this.child, }); final Widget child; final T data; // 定义一个便捷方法,方便子树中的widget获取共享数据 // static T of<T>(BuildContext context){ // final type = _typeOf<InheritedProvider<T>>(); // final provider = context.inheritFromWidgetOfExactType(type) as InheritedProvider<T>; // return provider.data; // } // 替换上面的便捷方法,按需求是否注册依赖关系 static T of<T>(BuildContext context, {bool listen = true}){ final type = _typeOf<InheritedProvider<T>>(); final provider = listen ? context.inheritFromWidgetOfExactType(type) as InheritedProvider<T> : context.ancestorInheritedElementForWidgetOfExactType(type)?.widget as InheritedProvider<T>; return provider.data; } _ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>(); } // _ChangeNotifierProviderState类的主要做用就是监听到共享状态(model)改变时从新构建Widget树。 // 注意,在_ChangeNotifierProviderState类中调用setState()方法,widget.child始终是同一个, // 因此执行build时,InheritedProvider的child引用的始终是同一个子widget, // 因此widget.child并不会从新build,这也就至关于对child进行了缓存!固然若是ChangeNotifierProvider父级Widget从新build时,则其传入的child便有可能会发生变化。 class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>>{ void update(){ // 若是数据发生变化(model类调用了notifyListeners),从新构建InheritedProvider setState(() => {}); } @override void didUpdateWidget(ChangeNotifierProvider<T> oldWidget){ // 当Provider更新时,若是新旧数据不相等,则解绑旧数据监听,同时添加新数据监听 if(widget.data != oldWidget.data){ oldWidget.data.removeListener(update); widget.data.addListener(update); } super.didUpdateWidget(oldWidget); } @override void initState(){ // 给model添加监听器 widget.data.addListener(update); super.initState(); } @override void dispose(){ // 移除model的监听器 widget.data.removeListener(update); super.dispose(); } @override Widget build(BuildContext context){ return InheritedProvider<T>( data: widget.data, child: widget.child, ); } } // 购物车示例:实现一个显示购物车中全部商品总价的功能 // 用于表示商品信息 class Item{ // 商品单价 double price; // 商品份数 int count; Item(this.price, this.count); } // 保存购物车内商品数据,跨组件共享 class CartModel extends ChangeNotifier{ // 用于保存购物车中商品列表 final List<Item> _items = []; // 禁止改变购物车里的商品信息 UnmodifiableListView<Item> get items => UnmodifiableListView(_items); // 购物车中商品的总价 double get totalPrice => _items.fold(0, (value, item) => value + item.count * item.price); // 将[item]添加到购物车,这是惟一一种能从外部改变购物车的方法 void add(Item item) { _items.add(item); // 通知监听者(订阅者),从新构建InheritedProvider,更新状态 notifyListeners(); } } // 优化 // 一个便捷类,封装一个Consumer的Widget class Consumer<T> extends StatelessWidget{ final Widget child; final Widget Function(BuildContext context, T value) builder; Consumer({ Key key, @required this.builder, this.child, }): assert(builder != null), super(key: key); Widget build(BuildContext context){ return builder( context, // 自动获取Model ChangeNotifierProvider.of<T>(context), ); } } // 页面 class ProviderRoute extends StatefulWidget{ _ProviderRouteState createState() => _ProviderRouteState(); } class _ProviderRouteState extends State<ProviderRoute>{ @override Widget build(BuildContext context){ return Scaffold( appBar: AppBar( title: Text('跨组件状态共享(Provider)'), ), body: Center( child: ChangeNotifierProvider<CartModel>( data: CartModel(), child: Builder(builder: (context){ return Column( children: <Widget>[ // Builder(builder: (context){ // var cart = ChangeNotifierProvider.of<CartModel>(context); // return Text("总价:${cart.totalPrice}"); // },), // 进行优化,替换上面Builder Consumer<CartModel>( builder: (context, cart) => Text("总价:${cart.totalPrice}"), ), Builder(builder: (context){ // 控制台打印出这句,说明按钮在每次点击时其自身都会从新build! print("RaisedButton build"); return RaisedButton( child: Text("添加商品"), onPressed: (){ // 给购物车中添加商品,添加后总价会更新 // ChangeNotifierProvider.of<CartModel>(context).add(Item(20.0, 1)); // listen设为false,不创建依赖关系,由于按钮不须要每次从新build ChangeNotifierProvider.of<CartModel>(context, listen: false).add(Item(20.0, 1)); }, ); },) ], ); },), ), ), ); } }
代码优化
两个地方能够进行代码优化,详细看代码示例。this
总结
Provider原理图
Model变化后会自动通知ChangeNotifierProvider(订阅者),ChangeNotifierProvider内部会从新构建InheritedWidget,而依赖该InheritedWidget的子孙Widget就会更新。 能够发现使用Provider,将会带来以下收益:spa
- 业务代码更关注数据了,只要更新Model,则UI会自动更新,而不用在状态改变后再去手动调用setState()来显式更新页面。
- 数据改变的消息传递被屏蔽了,咱们无需手动去处理状态改变事件的发布和订阅了,这一切都被封装在Provider中了。这真的很棒,帮咱们省掉了大量的工做!
- 在大型复杂应用中,尤为是须要全局共享的状态很是多时,使用Provider将会大大简化咱们的代码逻辑,下降出错的几率,提升开发效率。
其余状态管理包
Provider & Scoped Model、Redux、MobX、BLoC,这里特别推荐阿里咸鱼团队推出的开源项目:fish redux。code