本文由 简悦SimpRead 转码,原文地址 blog.gskinner.com
今天我们要看一下Flutter中比较流行的3个基本状态管理的库。G......
浏览器中的 "状态管理 "是指 "状态管理",而不是 "状态管理"。
今天我们来看看Flutter中基本状态管理的3个比较流行的库。GetIt,Provider和riverpod。
对于每一个库,我们将看看你如何执行数据绑定、数据注入,以及如何模拟它们进行测试。
配置...
在Flutter中,有许多类型的数据可以共享。期货、流、ValueNotifiers等。在这个例子中,我们将使用一个经典的基于 "ChangeNotifier "的管理器,它有两个可变的属性。它看起来就像这样。
class AppManager extends ChangeNotifier {
...
int _count1 = 0;
int get count => _count;
set count1(int value){
_count = value;
notifyListener();
}
int _count2 = 0;
set count2(int value){
_count2 = value;
notifyListener();
}
}
Provider
为了举例说明,设想我们还有两个对象需要传递:SettingsManager和FileService。另外,让我们假设设置和应用程序,都需要使用文件服务(这将让我们展示共享对象相互访问)。为了跟上下面的例子,查看github上的代码。
Provider在widget树中使用父:子关系传递数据。一个MultiProvider小组件可以用来传递多个对象到下面的小组件树中。
class ProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider(create: (_) => FileService()),
ChangeNotifierProvider(create: (context) {
return SettingsManager(() => context.read<FileService>());
}),
ChangeNotifierProvider(create: (context) {
return AppManager(() => context.read<FileService>());
}),
],
child: ...,
);
}
}
在视图层中,context.read扩展可以用来访问数据,context.watch或context.select可以用来与之绑定。
class View1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
void handleTap() => context.read<AppManager>().count1++;
int count = context.select((AppManager m) => m.count1);
...
}
}
这里的select调用将我们的部件与ChangeNotifier绑定,现在只要.count1属性发生变化,它就会重建,而忽略count2的变化。
使用提供者进行测试
要用Provider进行测试,你可以扩展你的数据对象来创建一个模拟,并在你想测试的widget上面提供这个模拟。
class MockFileService extends FileService { ... }
return MultiProvider(
providers: [
Provider(create: (_) => MockFileService()),
...
],
child: ...,
);
不幸的是,Provider没有任何系统允许你从 "外部 "覆盖依赖关系,这可能会使事情更难测试。
提供者的局限性
如前所述,Provider的可测试性是有限的,因为它没有内置的覆盖系统。它也很难在widget树之外工作,因为每次查找都需要context。最后,当它试图提供同一类型的多个对象时,会出现问题。
GetIt + GetItMixin
GetIt是经典的服务定位器 模式的实现,提供各种静态方法来注册单体或工厂。
final sl = GetIt.instance;
sl.registerSingleton(FileService());
sl.registerSingleton(SettingsManager(() => sl.get<FileService>()));
sl.registerSingleton(AppManager(() => sl.get<FileService>()));
get方法可以用来读取数据,GetItMixin包提供了各种方法来在你的视图中绑定它。在本例中,为了绑定到 "ChangeNotifier "的一个属性,我们可以使用 "watchOnly",我们从mixin中获得。
class View1 extends StatelessWidget with GetItMixin {
@override
Widget build(BuildContext context) {
void handleTap() => GetIt.I.get<AppManager>().count1++;
int count = watchOnly((AppManager m) => m.count1);
...
}
}
使用GetIt进行测试
使用GetIt进行测试,你可以使用unregister和register方法,随时随地提供一个模拟类,这使得测试变得轻而易举。
GetIt.I.unregister(GetIt.I.get<FileService>());
GetIt.I.register(MockFileService());
你也可以使用pushNewScope和popScope来轻松设置和拆除一组注册对象。
GetIt.I.pushNewScope();
GetIt.I.register(MockFileService());
// do tests
GetIt.I.popScope();
GetIt的局限性
GetIt也有提供相同类型的实例的问题,然而它在注册时暴露了一个name字段,这为这个问题提供了一个合理的解决方法。另一个潜在的弱点是,你可能不得不手动处置或取消注册项目,因为它们不是widget树的内在组成部分。最后,一些开发者可能不喜欢 "GetIt "单项的全局性,他们更喜欢 "riverpod "或 "Provider "的更严格的范围模型,而其他开发者可能认为这是个好处。
Riverpod
使用riverpod,你定义了全局可访问的 "提供者",然后你使用ref对象来读取它们。一个`提供者范围'也必须放置在widget树的周围。
final fileServiceProvider = Provider((_) => FileService());
final settingsManagerProvider = ChangeNotifierProvider((ref) {
return SettingsManager(() => ref.read(fileServiceProvider));
});
final appManagerProvider = ChangeNotifierProvider((ref) {
return AppManager(() => ref.read(fileServiceProvider));
});
class RiverpodExample extends StatelessWidget {
const RiverpodExample({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ProviderScope(
child: ...,
);
}
}
在声明了你的提供者后,你可以对ConsumerWidget进行子类化,并声明一个稍加修改的build方法,以获得一个ref,然后允许你读取提供者。
class View1 extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
void handleTap() => ref.read(appManagerProvider).count1++;
int count = ref.watch(appManagerProvider.select((p) => p.count1));
...
}
}
使用riverpod测试
riverpod提供了一个简单的覆盖机制,可以用来强制模拟到任何提供者。
ProviderScope overrides: [
fileServiceProvider.overrideWithValue(MockFileService())
],
child: ...,
);
riverpod的局限性
从功能上来说,riverpod没有太多的限制,它有很强的可测试性,并且允许轻松注入相同类型的对象。它最大的缺点可能是使用起来略显笨拙。它需要一个自定义的widget,一个修改过的构建方法签名,而且所有的读取都需要一个ref。虽然这在开始时感觉有点烦,但一旦你习惯了,就不是问题了。为了进一步缓解这个问题,riverpod还附带了一套用于Android Studio和VS Code的代码片段。
尾思...
真的,你用这些状态管理库中的任何一个都不会出错。你选择哪一个主要是个人品味的问题。GetIt采用了最简单的方法,并提供了逻辑对象和widget树的硬脱钩。riverpod在服务定位器公式上引入了一个强大的转折,它的 "许多定位器 "+范围方法,但不像其他的那样容易学习。最后但并非最不重要(好吧,基本上是最不重要)的是,Provider为您提供了不加修饰的、传统的 "继承小部件 "风格的方法,其API非常简单,在小部件树中非常有效,但在树外就不那么好了。
如果您有任何问题(或更正!),请在下面告诉我们。