Provider 原理介绍与实战(上)

1,251 阅读4分钟

这是我参与更文挑战的第18天,活动详情查看: 更文挑战

背景

整个Flutter都是Widget组成,单独页面数据共享很好解决,通过构造函数和回调就能解决;但是如果多页面数据同步就会异常的麻烦;

Flutter提供了一种状态管理方式,那就是 StatefulWidget。在 State 属于某一个特定的 Widget,在多个 Widget 之间进行交流的时候,虽然你可以使用 callback 解决,但是当嵌套足够深的话,我们增加非常多可怕的垃圾代码。

简单的App只是呈现数据话,StatefulWidge就足够了,如下图

img

但随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样

img

你肯定就会很崩溃,那在Flutter中数据共享,以及父子组件,兄弟组件之间怎么优雅的通信呢?

这里我们就要请出我们今天的主角Provider

什么是Provider?他的作用是什么?

Provider 包装的是InheritedWidget ,让我们的代码更容易使用和复用;如果直接使用InheritedWidget 每次都创建他的子类共享和管理数据;Provider 能够解决跨页面的数据问题,同时可以控制页面的刷新的粒度;

Provier 几个不错的好处

  1. 非常易用的API,帮助我们创建/销毁
  2. 延迟加载
  3. 大大减少每次创建新类模版的时间
  4. 非常友好配套调试工具(暂时未研究)
  5. 封装了简单通用好理解的消费API;其实是对InheritedWidget 的包装
    1. Provider.of
    2. Consumer
    3. Selecor
    4. context.watch
    5. context.read
namedescription
Provider最基础的Provider,可以提供Value类型数据共享
ListenableProvider特定的Listenable 监听对象. ListenableProvider 将要监听对象的改变,并且询问小组件依赖他进行重建,当他的调用者在被调用时候
ChangeNotifierProvider与ListenableProvider区别在于,当需要的时候,会自动调用dispose.
ValueListenableProvider与ListenableProvider区别在于,仅仅支持value方式获取状态。
StreamProvider与ListenableProvider区别在于,仅仅支持value方式获取状态。
FutureProvider与ListenableProvider区别在于,仅仅支持value方式获取状态。

使用介绍

ChangeNotifierProvider

  1. 使用注意⚠️

    MyChangeNotifier variable;
    ChangeNotifierProvider.value(
      value: variable,
      child: ...
    )
    
    
    ChangeNotifierProvider.value(
      create: MyModel(),
      child: ...
    )
    
    

MultiProvider

MultiProvider(
  providers: [
    Provider<Something>(create: (_) => Something()),
    Provider<SomethingElse>(create: (_) => SomethingElse()),
    Provider<AnotherThing>(create: (_) => AnotherThing()),
  ],
  child: someWidget,
)

消费数据

非常简单的方式读取 Provider中的数据, 通过BuildContext 中的扩展方法:Consumer watchreadselector

  1. Consumer Consumer2<A, B> Consumer3<A, B>

    Widget build(BuildContext context) {
       return ChangeNotifierProvider(
         create: (_) => Foo(),
       child: Text(Provider.of<Foo>(context).value),
       );
    }
    
    Widget build(BuildContext context) {
           return ChangeNotifierProvider(
             create: (_) => Foo(),
             child: Consumer<Foo>(
               builder: (_, foo, __) => Text(foo.value),
             },
           );
        }
    
  2. context.watch<T>(), 监听小部件上T数据的改变

    extension WatchContext on BuildContext {
      T watch<T>() {
        ///	隐藏了部分代码
        return Provider.of<T>(this);
      }
    }
    
  3. context.read<T>() 不会监听数据的改变

    /// Exposes the [read] method.
    extension ReadContext on BuildContext {
      T read<T>() {
           ///	隐藏了部分代码
        return Provider.of<T>(this, listen: false);
      }
    }
    
  4. context.select<T, R>(R cb(T value)) 容许组件监听一小部分数据T的改变

    或者使用静态方法 Provider.of<T>(context), 使用的方式和 watch/read相同

    class Home extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Text(
          // Don't forget to pass the type of the object you want to obtain to `watch`!
          context.watch<String>(),
        );
      }
    }
    

什么情况下应该使用哪种Provider?

  1. 不需要监听状态的改变,子组件不需要获取父组件的状态,使用Provider就足够

  2. 需要共享数据,并且监听数据的变化,可以使用ListenableProvider 或者 ChangeNotifierProvider, 如果需要自主管理数据的dispose,建议使用ListenableProvider

  3. 如果有两种数据类型,或者有多种数据依赖关系,使用ProxyProvider系列0

    /// 创建单一的[Provider]
      /// 使用这个方法时,需要在main.dart中添加 Provider.debugCheckInvalidValueType = null;
      static Provider createProvider<T>(T t) {
        return Provider<T>(
          create: (BuildContext c) => t,
        );
      }
    
     /// 创建单一的[ListenableProvider]
      static ListenableProvider createListenableProvider<T extends Listenable>(
          T t) {
        return ListenableProvider<T>(
          create: (BuildContext c) => t,
        );
      }
    
  4. Provider的原理是什么

    说道provider的原理,就需要先理解下系统InheritedWidget的机制

    InheritedWidget

    InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widget树中从上到下传递、共享的方式,比如我们在应用的根widget中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget中来获取该共享的数据!这个特性在一些需要在widget树中共享数据的场景中非常方便!如Flutter SDK中正是通过InheritedWidget来共享应用主题(Theme)和Locale (当前语言环境)信息的。

    class ShareDataWidget extends InheritedWidget {
      ShareDataWidget({
        @required this.data,
        Widget child
      }) :super(child: child);
    
      final int data; //需要在子树中共享的数据,保存点击次数
    
      //定义一个便捷方法,方便子树中的widget获取共享数据  
      static ShareDataWidget of(BuildContext context) {
        return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
        //return context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget;
      }
    
      //该回调决定当data发生变化时,是否通知子树中依赖data的Widget  
      @override
      bool updateShouldNotify(ShareDataWidget old) {
        //如果返回true,则子树中依赖(build函数中有调用)本widget
        //的子widget的`state.didChangeDependencies`会被调用
        return old.data != data;
      }
    }
    

    未完!

    下篇预告

    1. Provider在项目中怎么的使用

    2. Provider 在MVVM中的实践

参考

zhuanlan.zhihu.com/p/70280813

www.examplecode.cn/2020/05/09/…

www.flutterj.com/?post=137

book.flutterchina.club/chapter7/in…