依赖注入(DI)和状态管理之间的混合体,使用
小部件为小部件而构建。
它特意使用小部件进行DI/状态管理,而不是像Stream这样的纯Dart
类。
原因是,小部件非常简单,但又健壮且可扩展。
通过使用小部件进行状态管理,provider可以保证
- 可维护性,通过强制的单向数据流
- 可测试性/可组合性,因为总是可以模拟/覆盖一个
value - 健壮性,因为很难忘记处理模型/小部件的更新场景
模型/小部件
要了解更多关于provider的信息,请参阅文档。
从v2.0.0迁移到v3.0.0
- Providers现在不能再用
const实例化了。 Provider现在如果与Listenable/Stream一起使用会抛出异常。
请考虑改用ListenableProvider/StreamProvider。或者,
可以通过将
Provider.debugCheckInvalidValueType设置为null来禁用此异常,如下所示
void main() {
Provider.debugCheckInvalidValueType = null;
runApp(MyApp());
}
- 所有
XXProvider.value构造函数现在都使用value作为参数名。
之前
ChangeNotifierProvider.value(notifier: myNotifier),
之后
ChangeNotifierProvider.value(value: myNotifier),
StreamProvider的默认构造函数现在构建一个Stream而不是一个
StreamController。之前的行为已移至命名
构造函数StreamProvider.controller。
之前
StreamProvider(builder: (_) => StreamController<int>()),
之后
StreamProvider.controller(builder: (_) => StreamController<int>()),
用法
暴露一个值
要通过provider暴露一个变量,请将任何小部件包装到该包中的一个提供程序
小部件中,并将变量传递给它。然后,所有子孙小部件
新添加的提供程序小部件都可以访问此变量。
一个简单的例子是将整个应用程序包装在一个Provider
小部件中并传递我们的变量
Provider<String>.value(
value: 'Hello World',
child: MaterialApp(
home: Home(),
)
)
或者,对于复杂对象,大多数提供程序都公开一个接受创建值的函数的构造函数。提供程序将在小部件插入到树中时仅调用该函数一次,并公开结果。这对于公开一个永不随时间变化而又无需编写StatefulWidget的复杂对象非常完美。
该提供程序将调用该函数仅
一次,当插入小部件在树中,并公开结果。这是
完美的暴露一个复杂的对象,永不随时间变化,而无需
编写StatefulWidget。
以下内容创建并公开了一个MyComplexClass。并且在Provider被移除出小部件树的情况下,实例化的MyComplexClass将被处置。
Provider被移除
将被处置。
Provider<MyComplexClass>(
builder: (context) => MyComplexClass(),
dispose: (context, value) => value.dispose()
child: SomeWidget(),
)
读取值
读取值的最简单方法是使用静态方法
Provider.of<T>(BuildContext context).
此方法将从与传递的BuildContext相关联的小部件开始在小部件树中查找,并返回找到的类型为T的最近变量(如果找不到则抛出异常)。
从该小部件开始
T找到(或抛出,如果找不到)。
结合暴露值的第一个示例,这个
小部件将读取暴露的String并渲染“Hello World”。
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
/// Don't forget to pass the type of the object you want to obtain to `Provider.of`!
Provider.of<String>(context)
);
}
}
或者,除了使用Provider.of之外,我们还可以使用Consumer和Selector。
这些对于性能优化或难以获取提供程序子孙BuildContext的情况可能很有用。
获取BuildContext的子孙。
请参阅FAQ或Consumer
和Selector的文档
了解更多信息。
MultiProvider
在大型应用程序中注入多个值时,Provider可能会变得相当
嵌套
Provider<Foo>.value(
value: foo,
child: Provider<Bar>.value(
value: bar,
child: Provider<Baz>.value(
value: baz,
child: someWidget,
)
)
)
在这种情况下,我们可以使用MultiProvider来提高可读性
MultiProvider(
providers: [
Provider<Foo>.value(value: foo),
Provider<Bar>.value(value: bar),
Provider<Baz>.value(value: baz),
],
child: someWidget,
)
两个示例的行为严格相同。MultiProvider仅改变
代码的外观。
ProxyProvider
从3.0.0开始,有一个新的提供程序类型:ProxyProvider。
ProxyProvider是一个提供程序,它将其他提供程序中的多个值组合成一个新对象,并将结果发送给Provider。
与另一个提供程序相结合
那个新对象将在它所依赖的任何提供程序更新时进行更新。
更新。
下面的示例使用ProxyProvider根据来自另一个提供程序的计数器构建翻译。
计数器
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => Counter()),
ProxyProvider<Counter, Translations>(
builder: (_, counter, __) => Translations(counter.value),
),
],
child: Foo(),
);
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
它有多种变体,例如
-
ProxyProvidervsProxyProvider2vsProxyProvider3,等等。类名后面的数字是它所依赖的其他提供程序的数量
ProxyProvider依赖。 -
ProxyProvidervsChangeNotifierProxyProvidervsListenableProxyProvider,等等。它们的工作方式相似,但不是将结果发送到
Provider,ChangeNotifierProxyProvider会将值发送到ChangeNotifierProvider。
常见问题
在initState中获取Providers时出现异常。我该怎么办?
此异常发生是因为您正试图从一个生命周期中监听一个提供程序,该生命周期永远不会再次调用。
生命周期
这意味着您应该使用另一个生命周期
(didChangeDependencies/build),或者明确表示您不关心更新。
因此,而不是
您应该做
initState() {
super.initState();
print(Provider.of<Foo>(context).value);
}
您应该做
Value value;
didChangeDependencies() {
super.didChangeDependencies();
final value = Provider.of<Foo>(context).value;
if (value != this.value) {
this.value = value;
print(value);
}
}
它会在value更改时打印value。
或者您可以这样做
initState() {
super.initState();
print(Provider.of<Foo>(context, listen: false).value);
}
它将只打印一次value并忽略更新。
我使用ChangeNotifier,并在更新它时遇到异常,发生了什么?
这很可能是因为您正在修改ChangeNotifier,而此时它位于其子孙之中在小部件树构建过程中。
这种情况的一个典型例子是在启动http请求时,其中
将来被存储在通知器中
通知器
initState() {
super.initState();
Provider.of<Foo>(context).fetchSomething();
}
这是不允许的,因为修改是即时的。
这意味着一些小部件可能在突变之前构建,而另一些
小部件将在突变之后构建。
这可能导致UI不一致,因此不允许。
相反,您应该在会同等影响整个树的地方执行该突变
整个树
-
直接在提供程序/模型构造函数的
builder中class MyNotifier with ChangeNotifier { MyNotifier() { _fetchSomething(); } Future<void> _fetchSomething() async {} }
当没有“外部参数”时,这很有用。
-
异步地,在帧的末尾
initState() { super.initState(); Future.microtask(() => Provider.of<Foo>(context).fetchSomething(someValue); ); }
这稍微不太理想,但允许将参数传递给突变。
我必须使用ChangeNotifier来处理复杂状态吗?
不。
您可以使用任何对象来表示您的状态。例如,另一种
架构是使用Provider.value()结合StatefulWidget。
这是一个使用这种架构的计数器示例
class Example extends StatefulWidget {
const Example({Key key, this.child}) : super(key: key);
final Widget child;
@override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
int _count;
void increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Provider.value(
value: _count,
child: Provider.value(
value: this,
child: widget.child,
),
);
}
}
我们可以通过这样做来读取状态
return Text(Provider.of<int>(context).toString());
并用修改状态
return FloatingActionButton(
onPressed: Provider.of<ExampleState>(context).increment,
child: Icon(Icons.plus_one),
);
或者,您可以创建自己的提供程序。
我可以创建自己的Provider吗?
是的。provider公开了构成一个功能齐全的提供程序的所有小型组件。
这包括
SingleChildCloneableWidget,用于使任何小部件与MultiProvider一起工作。InheritedProvider,在执行Provider.of时获取的通用InheritedWidget。DelegateWidget/BuilderDelegate/ValueDelegate,用于处理
“MyProvider()创建对象”与“MyProvider.value()可以随时间更新”的逻辑。
这是一个用于使用ValueNotifier作为状态的自定义提供程序的示例
https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91
我的小部件重建得太频繁了,我该怎么办?
使用Consumer/Selector代替Provider.of。
它们可选的child参数允许只重建非常特定的小部件部分
树
Foo(
child: Consumer<A>(
builder: (_, a, child) {
return Bar(a: a, child: child);
},
child: Baz(),
),
)
在此示例中,只有Bar会在A更新时重建。Foo和Baz不会
不必要地重建。
更进一步,可以使用Selector来忽略更改,如果
它们不对小部件树产生影响
Selector<List, int>(
selector: (_, list) => list.length,
builder: (_, length, __) {
return Text('$length');
}
);
此片段仅在列表长度更改时重建。但它不会
在更新项时无意中更新。
我能获取同一类型的两个不同提供程序吗?
不能。虽然您可以拥有多个共享相同类型的提供程序,但一个小部件将只能获取其中一个:最近的祖先。
最近的祖先。
相反,您必须明确地为两个提供程序指定不同的类型。
而不是
Provider<String>(
builder: (_) => 'England',
child: Provider<String>(
builder: (_) => 'London',
child: ...,
),
),
首选
Provider<Country>(
builder: (_) => Country('England'),
child: Provider<City>(
builder: (_) => City('London'),
child: ...,
),
),
现有提供程序
provider公开了几种不同类型的“提供程序”,用于不同类型的对象。
所有可用对象的完整列表在此处:here
| 名称 | 描述 |
|---|---|
| Provider | 最基本的提供程序形式。它接收一个值并公开它,无论该值是什么。 |
| ListenableProvider | Listenable对象的特定提供程序。ListenableProvider将监听该对象,并在调用侦听器时要求依赖它的那些小部件重建。 |
| ChangeNotifierProvider | ChangeNotifier的ListenableProvider的规范。它将在需要时自动调用ChangeNotifier.dispose。 |
| ValueListenableProvider | 监听一个ValueListenable并仅公开ValueListenable.value。 |
| StreamProvider | 监听一个Stream并公开最新发出的值。 |
| FutureProvider | 接收一个Future并在Future完成时更新依赖项。 |