自定义状态管理Provider以及原理分析
Posted 小源子2016
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义状态管理Provider以及原理分析相关的知识,希望对你有一定的参考价值。
背景
在安卓中我们有时会使用EventBus事件总线的方式来进行消息直接的传递
在H5开发过程中,我们可以使用mobox作为响应式状态管理方案
在Flutter中,比较流行的是Provider,本文将结合Flutter源码谈谈InheritedElement和Provider
总结:
-
1.InheritedWidget提供了子孙获取其共享的数据以及和InheritedElement建立关系,
以便于InheritedElement的update变化的时候让子孙dirty
-
2.Provider利用了InhritedWidget机制,结合Listenable监听,在数据变化时
触发创建InheritedWidget,引起InheritedElement的update,从而让关联的子孙更新
-
3.我们做到了setState,数据的高效数据,避免了eventbus那样注册监听的繁琐
通过自定义Provider里理解上面的总结
下面我们使用自定义一个Provider,结合代码来加以说明,
学习的目标是充分理解InheritedWidget的作用和Provider的设计思想
首先入场的就是InheritedProvider
class InheritedProvider<T> extends InheritedWidget
final T data;
InheritedProvider(@required this.data, Widget child):super(child: child);
@override
bool updateShouldNotify(InheritedProvider<T> oldWidget)
return true;
相信大家和我会有这样的疑问,这玩意能干嘛,作用是什么?
这里我们不得不先介绍下Element(就是Context)的函数dependOnInheritedWidgetOfExactType
将来我们会这么调用 context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>() 这样可以获取到祖先widget,也就是InheritedProvider,并且将自身的element注入到祖先(就是InheritedProvider),以便于祖先在变化的时候将关联的element写为dirty
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>(Object aspect)
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if (ancestor != null)
assert(ancestor is InheritedElement);
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
_hadUnsatisfiedDependencies = true;
return null;
简单说明下,Element内部有个
Map<Type, InheritedElement> _inheritedWidgets;
里面存放了Element继承的祖先的Widget类型以及对应的Element对象
我们根据类型InheritedProvider这个祖先widget类型,找到其对应的element对象 ancestor,
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, Object aspect )
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
接着 调用ancesor这个祖先中的方法dependOnInheritedElement
class InheritedElement extends ProxyElement
/// Creates an element that uses the given widget as its configuration.
InheritedElement(InheritedWidget widget) : super(widget);
@override
InheritedWidget get widget => super.widget as InheritedWidget;
final Map<Element, Object> _dependents = HashMap<Element, Object>();
最后把使用使用方的context(element)的设置到了InheritedElement的
@override
void updated(InheritedWidget oldWidget)
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
然后InheritedElement在update的时候,会执行updateShoudNotify,决定是否通知_dependents
中的element,如果true,则再调用element的didChangeDependencies(内部会调markNeedsBuild();)从而导致关联的子孙的element重新build
如果是StatefulElement,还会同时的设置_didChangeDependencies为true,者也rebuild的时候
就会调用state的didChangeDependencies了
也就是说我们在获取祖先 InheritedProvider的同时,我们就将自身注册到了祖先当中,
将来祖先变化的时候,就会通知使用地方的Element重新build
貌似我们现在可以使用InheritedProvider了,我们获取里面的共享数据展示或者当点击的时候
setState,触发InheritedProvider重新构建,InheritedElement只会update(因为类型一致),
,然后通知其子孙刷新即可
但是这样做我们还是要setState,怎么避免呢?
看下面的代码,下面就要介绍Provider思想
Provider本身就是注册监听数据的变化,在感知数据变化的时候setState,这样就避免了外部
使用方再去setState
我们让T将来必须继承自ChangeNotifier,在T变化的时候通知监听者,以便于
告知订阅者provider去做setState和InheritedProvider重新创建(T成为了消息发布者)
class ChangeNotifier implements Listenable
List listeners = [];
@override
void addListener(VoidCallback listener)
// 添加监听器
listeners.add(listener);
@override
void removeListener(listener)
//移除监听器
listeners.remove(listener);
void notifyListeners()
// 通知所有监听器,触发监听器回调
listeners.forEach((element) element(););
ChangeNotifierProvider主要去订阅T的变化,在内部去setState
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget
final Widget child;
final T data;
ChangeNotifierProvider(
Key key,
this.data,
this.child,
);
// 定义一个便捷方法,方便子树中的widget获取共享数据
// T 最终被用来选择共享状态
static T of<T>(BuildContext context, bool listen = true)
final provider = listen?context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>()
: context.getElementForInheritedWidgetOfExactType<InheritedProvider<T>>()?.widget as InheritedProvider<T>;
;
return provider.data;
@override
_ChangeNotifierProviderState createState()
return _ChangeNotifierProviderState<T>();
class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider>
void update()
// 如果数据发生变化 ( model类调用了notifyListeners),重新构建InheritedProvider
setState(()
);
@override
void didUpdateWidget(ChangeNotifierProvider<ChangeNotifier> oldWidget)
// 当provider更新时,如果新旧数据不"==",则解绑旧数据监听,joint添加新数据监听
if(widget.data != oldWidget.data)
oldWidget.data.removeListener(update);
widget.data.addListener(update);
super.didUpdateWidget(oldWidget);
@override
void initState()
super.initState();
// 给model添加监听器
widget.data.addListener(update);
@override
void dispose()
super.dispose();
// 移除model的监听器
widget.data.removeListener(update);
@override
Widget build(BuildContext context)
///重新构建InheritedProvider,会导致依赖InheritedProvider
///中的widget会重新build
///估计这是因为Inherited会记录依赖关系,通过依赖者Element(Context)重新build
///这个是Inherited的核心
return InheritedProvider<T>(
data: widget.data,
child: widget.child,
/// 理解widget child是缓存,一般不会重新创建
/// 起子孙若依赖了InheritedWidget数据,则子孙自己会build
);
使用方则主要通过ChangeNotifierProvider构建,那么它的祖先自然就是InheritedProvider,
就可以通过前面说的element函数获取InheritedProvider数据,而和祖先建立了联系,祖先
update的时候会通知依赖了祖先的element,也就是调用依赖方element的didChangeDependencies,标记其为dirty,从而导致其刷新,我们操作T的变化,通知了订阅者
Provider,Provider内部封装了setState
注意:
1.widget.child是外部传入的,provider的setState本身不会导致widget.child的build
这是个很好的思想,通过缓存widget的方式,可以避免一些widget不必要的build,提升性能,
在Flutter开发过程中要学会适应缓存widget的思路
2.如果不想和祖先建立变化联系,可以使用getElementForInheritedWidgetOfExactType,只是获取祖先数据,比较按钮点击事件处理,本身不需要监听祖先数据的变化
消费方代码
注意:Cosumer本身是封装了of获取的逻辑,将widget构建封装在了一段函数中,函数中的参数带有共享状态数据,这样可以语义更明确
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../inherited_provider.dart';
import 'CartModel.dart';
import 'Item.dart';
class ProviderRoute extends StatefulWidget
@override
_ProviderRouteState createState()
return _ProviderRouteState();
class Consumer<T> extends StatelessWidget
final Widget Function(BuildContext context, T value) builder;
Consumer(Key key, @required this.builder): super(key: key);
@override
Widget build(BuildContext context)
var data = ChangeNotifierProvider.of<T>(context);
return builder(context, data);
class _TestWiget extends StatelessWidget
@override
Widget build(BuildContext context)
return Builder(builder: (context)
print("1232323");
return Column(children: <Widget>[
Builder(builder: (context)
///通过dependOnInheritedWidgetOfExactType获取InheritedProvider<T>
///然后获取共享状态数据T
///
// var cart = ChangeNotifierProvider.of<CartModel>(context);
// return Text("总价:$cart.totalPrice");
/// 一段写死的Text 变成了一个builder函数返回
/// builder 函数内部获取了T
return Consumer<CartModel>(builder:(_, cart)
return Text("总价:$cart.totalPrice");
);
,),
Builder(builder: (context)
print("RaiseButton build"); //在后面优化部分回用的
return RaisedButton(
child: Text("添加商品"),
onPressed: ()
//给购物车中添加商品,添加后总结会更新
/// 添加通知监听者,监听方就是ChangeNotifierProvider,
/// 其内部调用setState,
ChangeNotifierProvider.of<CartModel>(context, listen: false).add(Item(20.0, 1));
,
);
,)
],);
);
class _ProviderRouteState extends State<ProviderRoute>
@override
Widget build(BuildContext context)
return Center(
/// 中间件ChangeNotifierProvider创建了InheritedProvider
/// 使用方传入了ChangeNotifer CartModel
///
///
/// 当ChangeNotifierProvider监听到数据变化
/// 会执行setState,但是其child是外部传入,始终用的还是上一个
/// 实际结果就是widget.child不会重复执行build,
/// 但是用到了共享状态数据的某个孙子地方确实会重新build,
///
/// 如果ChangeNotifierProvider的 parent重新build
/// 那么就可能会导致重新生成widget.child,也就是_TestWiget()
///
/// ChangeNotifierProvider这个定义在应用的根节点,可以实现跨路由共享
///
/// 优点分析
///1.避免了setState,完整了自动刷新(得益于provider内部监听和setState)
///2.数据变化传递被隐藏,不需要专门监听使用数据即可(消息的变化监听以及封装在provider了,
///当发生变化的时候,InheritedProvider重新创建,并且会让依赖方重新build
///3.大型复杂应用中,尤其是需要全局共享的状态非常多时,Provider可以简化
///代码逻辑,提高开发效率
///)
///
child: ChangeNotifierProvider<CartModel>(
data: CartModel(),
child: _TestWiget(),),
);
import 'dart:collection';
import '../inherited_provider.dart';
import 'Item.dart';
class CartModel extends ChangeNotifier
// 用于保存购物车中的商品列表
final List<Item> _items = [];
// 禁止改变购物车李的上哦信息
//UnmodifiableListView
/// 外部访问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();
class Item
double price; // 商品单价
int count; //商品份数
Item(this.price, this.count);
以上是关于自定义状态管理Provider以及原理分析的主要内容,如果未能解决你的问题,请参考以下文章