Flutter Provider

InheritedWidget 组件是功能型组件,提供了沿树向下,共享数据的功能,即子组件可以获取父组件(InheritedWidget 的子类)的数据,通过BuildContext.dependOnInheritedWidgetOfExactType 获取。

class MyInheritedWidget extends InheritedWidget {
  final UserInfo userInfo;

  MyInheritedWidget({this.userInfo, Widget child}):super(child: child);

  static MyInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }

  @override
  bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
    return oldWidget.userInfo != userInfo;
  }
}

updateShouldNotify 方法必须重写,此方法是判断新的共享数据和原数据是否一致,是否将通知传递给所有子组件(已注册)。重建此组件时,有时需要重建继承 InheritedWidget 组件的组件,但有时不需要。 例如,如果此组件所保存的数据与“ oldWidget”所保存的数据相同,则我们无需重建继承了“ oldWidget”所保存的数据的组件。

class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
  UserInfo _userInfo = UserInfo(name: 'lisa', age: 18);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InheritedWidget Demo'),
      ),
      body: Center(
        child: MyInheritedWidget(
          userInfo: _userInfo,
          child: A(
            child: F(),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _userInfo = UserInfo(name: '小明', age: 18);
          });
        },
      ),
    );
  }
}

下面是A和F

class F extends StatefulWidget {
  @override
  _FState createState() => _FState();
}
class _FState extends State<F> {
  @override
  void initState() {
    super.initState();
    print('F initState');
  }
  @override
  Widget build(BuildContext context) {
    print('F build');
    return Text('name:${MyInheritedWidget.of(context).userInfo.name}');
  }
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('F didChangeDependencies');
  }
}

class A extends StatefulWidget {
  final Widget child;
  const A({Key key, this.child}) : super(key: key);
  @override
  _AState createState() => _AState();
}

class _AState extends State<A> {
  @override
  void initState() {
    super.initState();
    print('A initState');
  }

  @override
  Widget build(BuildContext context) {
    print('A build');
    return Center(
      child: widget.child,
    );
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('A didChangeDependencies');
  }
 
}

修改_userInfo时依赖 MyInheritedWidget 组件的 F 组件调用 didChangeDependencies 方法,而 A 组件没有调用 didChangeDependencies 方法,因为 A 没有依赖 MyInheritedWidget 组件。

F 组件使用 InheritedWidget 的共享数据并展示,如果 updateShouldNotify 返回 false,那么 F 组件 rebuild 时只会执行 build 函数,而 updateShouldNotify 返回 true时, F 组件 rebuild 时会执行 didChangeDependencies 和 build 函数,因此可以将类似访问服务器接口这种耗时工作放在 didChangeDependencies 函数中,这也是 didChangeDependencies 生命周期存在的意义。

使用方法

Step1:添加依赖
dependencies:
  flutter:
    sdk: flutter
   provider: ^4.0.4
Step:创建一个ChangeNotifier

我们先新建一个 NumberManager,继承 ChangeNotifier,使之成为我们的数据提供者之一。

class NumberManager with ChangeNotifier, DiagnosticableTreeMixin {
  int _firstNum = 0;
  int _secondNum = 0;

  int get firstNum => _firstNum;
  int get secondNum => _secondNum;

  void changeData1() {
    _firstNum++;
    notifyListeners();
  }

  void changeData2() {
    _secondNum++;
    notifyListeners();
  }
}
Step5:创建ChangeNotifierProvider
List<SingleChildWidget> providerList = [
   ChangeNotifierProvider(create: (context) => NumberManager()),
];

void main() {
  // ignore: prefer_const_constructors
  runApp(MultiProvider(
    providers: providerList,
    child: MyApp(),
  ));
  // runApp(const MyApp());
}

获取数据

1. 使用Provider.of<T>(context)方法来引用数据

  • read读取数据,如果数据,不会主动调用build,所以数据变化后,显示的数据再没有重新build前,显示的还是之前的数据:
context.read()
 T read<T>() {
    return Provider.of<T>(this, listen: false);
  }
  • Provider.of<T>(context) 可以简写为context.watch(),
context.watch()
T watch<T>() {
   return Provider.of<T>(this);
 }
}

通过此方法应用数据时,当前widget所在的build会在收到通知时进行重绘,所以在使用过程中如果不希望当前widget所在的类进行rebuild,可以为当前widget单独创建一个StatelessWidget:

class FirstNumWidget extends StatelessWidget {
  const FirstNumWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print("----------FirstNumWidget被重执行了----------");

    //  使用Provider.of<T>(context)方法来引用数据,每次数据变化都会重新build
    return ElevatedButton(
      child: Text(
          "consumer firstNum的值:${Provider.of<NumberManager>(context).firstNum}"),
      onPressed: () {
        Provider.of<NumberManager>(context, listen: false).changeData1();
      },
    );
  }
}

Consumer

具体用法如下,builder 中的参数分别是 Context context, T value, Widget child,value 即Model1,value 的类型和 Model1 类型一致,builder 方法返回的是 Widget,也就是被 Consumer 包裹的 widget,当监听的 model 值发生改变,此 widget 会被 Rebuild。

Consumer<Model>(
        builder: (context, model, child) {
          return Text('Model count:${model.count}');
        },
 )

Selector

Selectord定义

class Selector<A, S> extends Selector0<S> {
    Key key,
    //  Widget Function(BuildContext context, T value, Widget child)
    // 用于构建 Widget
    @required ValueWidgetBuilder<S> builder,
    // S Function(BuildContext, A)
    // 用于指定使用哪个值作为重重建判断依据
    @required S Function(BuildContext, A) selector,
    // bool Function(T previous, T next)
    // 是否重建,一般情况下不用我们实现
    ShouldRebuild<S> shouldRebuild,
    Widget child,
  })

Selector 和 Consumer 很相似,唯一不同的是,Selector 可以自定义返回类型 ,所以Selector可以自定义是否在收到通知时进行页面重绘

Selector<Model1, int>(
 builder: (context, count, child) => Text(
   "Selector示例演示: $count",
   style: Theme.of(context).textTheme.title,
 ),
 selector: (context, model) => model.count,
),

Selector 有 Selector0 ~ Selector6,具体使用大同小异,可以传 0 ~ 6 个 ChangeNotifer,但是 S(Value)泛型只有一个,如果需要多个个值同时判断返回 S 怎么办呢?这个时候可以使用元祖 Tuple

总结

Consumer 可以避免 widget 多余的 rebuild,当 Consumer 中监听的 value 不发生变化,其包裹的 widget 不会 Rebuild。 Selector 在 Consumer 基础上提供了更加精确的监听,还支持自定义 rebuild,可以更加灵活地控制 widget rebuild 问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容