自定义状态管理Provider以及原理分析

Posted 小源子2016

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义状态管理Provider以及原理分析相关的知识,希望对你有一定的参考价值。

背景

在安卓中我们有时会使用EventBus事件总线的方式来进行消息直接的传递

在H5开发过程中,我们可以使用mobox作为响应式状态管理方案

在Flutter中,比较流行的是Provider,本文将结合Flutter源码谈谈InheritedElement和Provider

总结:

  1. 1.InheritedWidget提供了子孙获取其共享的数据以及和InheritedElement建立关系,

    以便于InheritedElement的update变化的时候让子孙dirty

  2. 2.Provider利用了InhritedWidget机制,结合Listenable监听,在数据变化时

    触发创建InheritedWidget,引起InheritedElement的update,从而让关联的子孙更新

  3. 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以及原理分析的主要内容,如果未能解决你的问题,请参考以下文章

Flutter之夸组件共享状态Provider原理剖析

Flutter之跨组件共享状态Provider原理剖析

VSCode自定义代码片段13——Vue的状态大管家

VSCode自定义代码片段13——Vue的状态大管家

VSCode自定义代码片段13——Vue的状态大管家

社区说|Flutter 主流状态管理框架 provider get 分析