关于Provider的思考
软件做三件事情
- 数据如何流转。对数据(本地和远程)的增删改查,以及内存数据落盘(包括本地存储设备以及远程存储设备)。
- 命令如何传递。对数据的操作(增删改查)以及页面的跳转。
- 数据如何展示。页面的跳转,不同的页面展示不同的业务功能,不同的形式的数据。
状态管理
状态管理:显示给用户的数据的管理。数据分为两种:一种是瞬时数据(保存在内存,不会落盘,应用重启数据丢失);另一种落盘数据,应用重启时候数据不会丢失,可以重新加载。两种数据类型仅仅是因为产品定义的不同导致的。例如:一个电商应用,有用户中心,购物车,首页三个标签,如果需要记录保存用户选中标签页索引,直接落盘保存,下次重启应用就直接显示选中的标签页;否则,就是瞬时数据。
命令式UI和声明式UI在UI更新方面的不同之处
之前做Android原生开发时候更新UI的方式:findViewById或者直接使用保存的属性变量,然后调用相关的方法进行设置属性来改变UI。原来的View一直不改变,只是改变相应的属性。
Flutter声明式UI更新方式:Widget组件是不可变的,而且是轻量级的。改变UI会创建新的Widget组件实例。 所以在 Flutter 中有这么一种说法: UI = f(state):
状态管理的优势: 让开发者摆脱组件的繁琐控制,聚焦于状态处理。
InheritedWidget
幸运的是Flutter在widget中存在一种机制,能够为其子孙节点提供数据和服务。(换言之,不仅仅是它的子节点,所有在它下层的widget都可以)。就像你所了解的, Flutter中的一切都是widget。这里的机制也是一种widget—InheritedWidget, InheritedNotifier, InheritedModel等等。InheritedWidget这种机制就是Provider的基础。InhritedWidget底层使用了我们经常讨论的观察者数据模式,被观察者数据有变化的时候通知观察者update。
Provider简介
Provider是对InheritedWidget组件的上层封装,使其更易用,更易复用。让ChangeNotifier来处理数据,从而减少InheritedWidget大量的模版代码。 Talk is cheap. Show me the code.示例代码如下:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
/// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier].
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => Counter(),
child: const MyApp(),
),
);
// runApp(
// /// Providers are above [MyApp] instead of inside it, so that tests
// /// can use [MyApp] while mocking the providers
// MultiProvider(
// providers: [
// ChangeNotifierProvider(create: (_) => Counter()),
// ],
// child: const MyApp(),
// ),
// );
}
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
// ignore: prefer_mixin
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
/// Makes `Counter` readable inside the devtools by listing all of its properties
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('count', count));
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <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({super.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.headlineMedium,
);
}
}
- ChangeNotifier:被监听数据应该继承此类,数据更新时候需要通知监听者。
- ChangeNotifierProvider:它具有与ChangeNotifier相比的更改。子小组件可以访问状态对象并侦听更改。
- Consumer:在侦听状态更改时重建子树的一部分。最好能把 Consumer放在widget树层级尽量低的位置上,Consumer包裹的widget遇到ChangeNotifier数据变化的时候,都会重新构建Consumer包裹的widget树,如果层级过高,频繁更新会引起性能问题���
- Provider.of:允许后代小组件访问状态对象。能够监听变化,同时还能更改ChangeNotifier中的数据。
- MultiProvider能够通过组合ChangeNotifier的数组来提供多种类型的数据。
Provider的优缺点
优点:
- 使用简单。模型类继承ChangeNotifier,没有更多的布局widget,只需要通过context.read/context.watch操作或者监听模型类即可;
- 颗粒度把控简单。为了解决widget重新build太频繁的问题,官方推出了context.select来监听对象的部分属性。也可使用Consumer/Selector进行布局;
- 基于官方InheritedWidget的封装,不存在任何风险,很稳定且不会给性能方面加负担。
缺点:BuildContext强关联,BuildContext大多时候基本都是在widget中才能获取到,在其他地方想随时获取BuildContext是不切实际的。难以处理大型应用中的复杂状态;不支持异步操作;不支持共享状态跨widget树(当然使用ChangeNotifierProvider.value()方法来复用ChangeNotifierProvider实例,常驻内存达到跨widget的目的,但是会引起其他问题)。
总结
Provider是一个简洁优秀的状态管理框架,以后有很多优秀的状态管理框架都有Provider的影子,在 BLoC 与 Mobx 架构中使用 provider。Get管理框架也是一个非常实用的状态管理框架,全局单例(不受BuildContext影响),任意位置可以存取;存在类型重复,内存回收问题。希望我的文章对各位都有帮助,如果有问题可以不吝指教,祝好。
转载自:https://juejin.cn/post/7385776247601414154