flutter数据共享系列——随记(flutter分享功能)
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. 区分watch
和read
的使用
watch
和read
是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的区别在于获取数据的方式是getElementForInheritedWidgetOfExactType
ordependOnInheritedElement
。dependOnInheritedElement
会新增一个注册,这个注册会调用在数据变更后,调用消费者的didChangeDependencies
。稍微具体点分析可以查看我之前的文章—— 记InheritedWidget使用思考
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
使用,这里就不探讨了。
作者:reloadRen
链接:https://juejin.cn/post/7029608742987497502