连接 Widget 和 Provider 的桥梁:Consumer
一句话总结
“Consumer” 是一种特殊的 Widget,它就像一座桥,把 Widget 世界和 Provider 世界连接了起来。
“Consumer” 是一种特殊的 Widget,它就像一座桥,把 Widget 世界和 Provider 世界连接了起来。
Consumer 和普通的 Widget 之间唯一的真正区别就是:Consumer 能拿到一个 Ref 对象。这使得它们能够读取 Provider 的状态并监听其变化。详情请参阅 Refs。
Consumer 有几种不同的“口味”,主要是为了满足不同的个人偏好。你会发现:
Consumer,一个“建造者”风格的 Widget(类似于FutureBuilder)。它允许你的 Widget 与 Provider 互动,而无需继承StatelessWidget或StatefulWidget之外的任何东西。dart// 我们像往常一样继承 StatelessWidget class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { // 一个类似 FutureBuilder 的 widget return Consumer( // "builder" 回调给了我们一个 "ref" 参数 builder: (context, ref, _) { // 我们可以用这个 "ref" 来监听 provider final value = ref.watch(myProvider); return Text(value.toString()); }, ); } }ConsumerWidget,StatelessWidget的一个变体。你不再继承StatelessWidget,而是继承ConsumerWidget。它的行为完全相同,只是build方法多了一个WidgetRef参数。dart// 我们继承 ConsumerWidget 而不是 StatelessWidget class MyWidget extends ConsumerWidget { // "build" 方法多了一个参数 @override Widget build(BuildContext context, WidgetRef ref) { // 我们可以用这个 "ref" 来监听 provider final value = ref.watch(myProvider); return Text(value.toString()); } }ConsumerStatefulWidget,StatefulWidget的一个变体。同样,你不再继承
StatefulWidget,而是继承ConsumerStatefulWidget。并且,你不再继承State,而是继承ConsumerState。其独特之处在于ConsumerState有一个ref属性。dart// 我们继承 ConsumerStatefulWidget 而不是 StatefulWidget class MyWidget extends ConsumerStatefulWidget { @override ConsumerState<MyWidget> createState() => _MyWidgetState(); } // 我们继承 ConsumerState 而不是 State class _MyWidgetState extends ConsumerState<MyWidget> { // 现在有了一个 "this.ref" 属性可用 @override Widget build(BuildContext context) { // 我们可以用这个 "ref" 来监听 provider final value = ref.watch(myProvider); return Text(value.toString()); } }
此外,你还可以在 hooks_riverpod 包中找到额外的 Consumer。它们将 Riverpod 的 Consumer 与 flutter_hooks 结合了起来。如果你对 hooks 不感兴趣,可以忽略它们。
我应该用哪一个?
选择哪种 Consumer 主要取决于个人喜好。你完全可以只用 Consumer 来搞定一切。它比其他选项稍微冗长一些,但如果你不喜欢 Riverpod “侵入” StatelessWidget 和 StatefulWidget 的方式,那么这点代价是值得的。
但如果你没什么特别的偏好,我们推荐使用 ConsumerWidget(或者当你需要一个 State 时,使用 ConsumerStatefulWidget)。
为什么我们不能像 provider 包那样用 context.watch?
在像 provider 这样的替代包中,你可以使用 context.watch 来监听 Provider。只要你有 BuildContext,这在任何 Widget 中都行得通。那为什么 Riverpod 不这么做呢?
原因在于,如果纯粹依赖 BuildContext 而不是 Ref,就无法可靠地实现 Riverpod 的自动销毁功能。虽然有一些小技巧可以实现一个在 BuildContext 下“大部分时间能用”的版本,但问题是存在大量微妙的边缘情况,可能会悄无声息地破坏自动销毁功能。
这会导致内存泄漏,但这还不是真正的问题所在。
自动销毁更重要的意义在于,停止执行那些不再需要的代码。如果自动销毁失败,那么那个 Provider 可能会在后台持续不断地执行网络请求。
为了追求极致的可靠性,Riverpod 宁愿牺牲一点点便利性。