flutter数据共享系列——随记

Provider

InheritedWidget 解决了数据共享问题。迎面也带来数据刷新导致的组件不必要更新问题。Provider基于InheritedWidget实现数据共享,数据更新,定向告诉组件更新等。

接下来咱们先从Provider应用开始切入,逐渐剖析Provider的实现,以及对组件的利用进行相熟。

就拿官网文档开始:

新建一个模型Counter

class Counter with ChangeNotifier {  int _count = 0;  int get count => _count;  void increment() {    _count++;    notifyListeners();  }}

在适合的地位初始化

这里咱们抉择main办法:

void main() {  runApp(    MultiProvider(      providers: [        ChangeNotifierProvider(create: (_) => Counter()),      ],      child: const MyApp(),    ),  );}

应用并批改数据

class MyApp extends StatelessWidget {  const MyApp({Key? key}) : super(key: key);  @override  Widget build(BuildContext context) {    return const MaterialApp(      home: MyHomePage(),    );  }}class MyHomePage extends StatelessWidget {  const MyHomePage({Key? key}) : super(key: key);  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: const Text('Example'),      ),      body: Center(        child: Column(          mainAxisSize: MainAxisSize.min,          mainAxisAlignment: MainAxisAlignment.center,          children: const <Widget>[            Text('You have pushed the button this many times:'),             Extracted as a separate widget for performance optimization.             As a separate widget, it will rebuild independently from [MyHomePage].                         This is totally optional (and rarely needed).             Similarly, we could also use [Consumer] or [Selector].            Count(),          ],        ),      ),      floatingActionButton: FloatingActionButton(        key: const Key('increment_floatingActionButton'),         Calls `context.read` instead of `context.watch` so that it does not rebuild         when [Counter] changes.        onPressed: () => context.read<Counter>().increment(),        tooltip: 'Increment',        child: const Icon(Icons.add),      ),    );  }}class Count extends StatelessWidget {  const Count({Key? key}) : super(key: key);  @override  Widget build(BuildContext context) {    return Text(         Calls `context.watch` to make [Count] rebuild when [Counter] changes.        '${context.watch<Counter>().count}',        key: const Key('counterState'),        style: Theme.of(context).textTheme.headline4);  }}

应用注意事项

1. 共享数据定义为公有属性,提供get办法和update办法

这样能够无效的爱护数据结构,对立批改入口和获取办法。

2. 适当隔离会进行rebuild的组件,罕用的形式有三种:

  • 独自封装组件
  • 通过Consumer包裹
  • 通过Selector包裹,selector能够在某些值不变的状况下,避免rebuild。罕用的中央是针对列表中个别数据进行批改。

3. 辨别watchread的应用

watchread是Provider框架外部对BuildContext的扩大类。用户获取父级组件指定数据入口。区别在于是否增加了linsten,这个关系到是否须要实时刷新。
简略辨别两种场景:

  • watch:界面监听数据,更新页面
  • read:响应业务交互,去操作更新数据

两种办法的源码也很简略,只是为了不便生成的拓展类:

 Exposes the [read] method.extension ReadContext on BuildContext {  T read<T>() {    return Provider.of<T>(this, listen: false);  }} Exposes the [watch] method.extension WatchContext on BuildContext {  T watch<T>() {    return Provider.of<T>(this);  }}

这里咱们来看看Provider.of源码:

  static T of<T>(BuildContext context, {bool listen = true}) {// 移出局部不必要代码    final inheritedElement = _inheritedElementOf<T>(context);    if (listen) {      context.dependOnInheritedElement(inheritedElement);    }    return inheritedElement.value;  }      static _InheritedProviderScopeElement<T> _inheritedElementOf<T>(    BuildContext context,  ) {    // 移出局部不必要代码    _InheritedProviderScopeElement<T>? inheritedElement;    if (context.widget is _InheritedProviderScope<T>) {      context.visitAncestorElements((parent) {        inheritedElement = parent.getElementForInheritedWidgetOfExactType<            _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?;        return false;      });    } else {      inheritedElement = context.getElementForInheritedWidgetOfExactType<          _InheritedProviderScope<T>>() as _InheritedProviderScopeElement<T>?;    }    if (inheritedElement == null) {      throw ProviderNotFoundException(T, context.widget.runtimeType);    }    return inheritedElement!;  }

原来加不加listen的区别在于获取数据的形式是getElementForInheritedWidgetOfExactTypeordependOnInheritedElementdependOnInheritedElement会新增一个注册,这个注册会调用在数据变更后,调用消费者的didChangeDependencies 。略微具体点剖析能够查看我之前的文章——
[记InheritedWidget应用思考
](https://rzrobert.github.io/20...)

ChangeNotifier

实现Listenable接口的一个简略类,官网给的阐明很简略:

A class that can be extended or mixed in that provides a change notification

能够扩大或混合的类,提供更改告诉

实现了算法复杂度为O(1)去增加监听,O(N)去移除监听。对数据更新高效告诉页面去刷新。provider的数据模型均得继承与它。

ChangeNotifierProvider

有了数据模型,接下来就开始创立咱们的ChangeNotifier,就要用到ChangeNotifierProvider

先说一个谬误的示例,谬误的示例,谬误的示例,在build中通过ChangeNotifierProvider.value去创立:

 ChangeNotifierProvider.value(   value: new MyChangeNotifier(),   child: ... )

这样会造成内存泄露和潜在的bug——参考。

当然,这个办法存在必定是有他的意义的——如果你曾经有了ChangeNotifier实例,就能够通过ChangeNotifierProvider.value进行结构。而不是选用create

正确的办法是通过creat办法去构建:

 ChangeNotifierProvider(   create: (_) => new MyChangeNotifier(),   child: ... )

不要传入变量去构建ChangeNotifier,这样的话,当变量更新,ChangeNotifier是不会去更新的。

int count;ChangeNotifierProvider(  create: (_) => new MyChangeNotifier(count),  child: ...)

如果你的确须要传入变量,请应用ChangeNotifierProxyProvider。具体ChangeNotifierProxyProvider应用,这里就不探讨了。