• 【Flutter】功能型组件之跨组件状态共享


    前言

      在Flutter开发中,状态管理是一个永恒的话题。
      一般的原则是:如果状态是组件私有的,则应该由组件自己管理;如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。
      对于组件私有的状态管理很好理解,但对于跨组件共享的状态,管理的方式就比较多了,如使用全局事件总线EventBus,它是一个观察者模式的实现,通过它就可以实现跨组件状态同步:状态持有方(发布者)负责更新、发布状态,状态使用方(观察者)监听状态改变事件来执行一些操作。
      但是观察者模式来实现跨组件状态共享有一些明显的缺点:

    1. 必须显示定义各种事件,不好管理;
    2. 订阅者必须显式注册状态改变回调,也必须在组件销毁时手动去解绑回调以避免内存泄漏;

      是否还有更好的跨组件状态管理方式了?
      我们知道,InheritedWidget能绑定与它依赖的子孙组件的依赖关系,并且当InheritedWidget数据发生变化时,可以自动更新依赖的子孙组件。基于此,可以将需要跨组件共享的状态保存在InheritedWidget中,然后在子组件中引用InheritedWidget即可。Flutter社区的Provider包就是基于这个思想实现的。

    Provider

      基于上面的思想,实现一个最小功能的Provider。

    定义一个保存共享数据的类

      为了通用性,使用泛型。

    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

      存在两个问题:

    1. 数据发生变化怎么通知?
    2. 谁来重新构建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便有可能会发生变化。

    代码示例

    // 跨组件状态共享(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));
                        },
                      );
                    },)
                  ],
                );
              },),
            ),
          ),
        );
      }
    }
    
    

    代码优化

      两个地方可以进行代码优化,详细看代码示例。

    总结

    Provider原理图


      Model变化后会自动通知ChangeNotifierProvider(订阅者),ChangeNotifierProvider内部会重新构建InheritedWidget,而依赖该InheritedWidget的子孙Widget就会更新。
      可以发现使用Provider,将会带来如下收益:

    1. 业务代码更关注数据了,只要更新Model,则UI会自动更新,而不用在状态改变后再去手动调用setState()来显式更新页面。
    2. 数据改变的消息传递被屏蔽了,我们无需手动去处理状态改变事件的发布和订阅了,这一切都被封装在Provider中了。这真的很棒,帮我们省掉了大量的工作!
    3. 在大型复杂应用中,尤其是需要全局共享的状态非常多时,使用Provider将会大大简化我们的代码逻辑,降低出错的概率,提高开发效率。

    其他状态管理包

      Provider & Scoped Model、Redux、MobX、BLoC,这里特别推荐阿里咸鱼团队推出的开源项目:fish redux。

  • 相关阅读:
    如何更高效(HOWTO: Be more productiveAaron Swartz)
    这事儿太丢人了
    2012年终极心愿之每月培养一个好习惯
    【转载】SQLLite使用入门
    【原创】XNA 4.0学习笔记之如何使用XACT给Cue添加多个音频
    【原创】编程日记之——如何对DataSet进行强类型化
    【索引】Win7 操作系统编程常识
    [原创]怎么降低Winform程序占用的内存
    【原创】自制Winform标签控制控件[支持不规则窗体]
    【原创】XNA 4.0学习笔记之绘制基元图形的几种PrimitiveType区别[PS:4.0似乎有所改动]
  • 原文地址:https://www.cnblogs.com/parzulpan/p/12202577.html
Copyright © 2020-2023  润新知