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应用思考
](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
应用,这里就不探讨了。
发表回复