[译][官方文档] Flutter/Dart 状态管理库 Riverpod - 概要 - 常见问题
!!!译文为作者本人人肉翻译~转载请注明出处!!!
原文链接:FAQ | Riverpod
pub:riverpod | Dart Package (flutter-io.cn)
译时版本: 2.4.9
FAQ
这里有一些社区中常被问到的问题:
ref.refresh
和 ref.invalidate
有什么区别?
你可能已经注意到 ref
有两个方法强制 provider 进行重新计算,然后想知道它们的区别。
这比你想的要简单:ref.refresh
只是 invalidate
+ read
的语法糖:
T refresh<T>(provider) {
invalidate(provider);
return read(provider);
}
如果你不关心 provider 重新计算后的新值,那可以用 invalidate
。
如果需要这个新值,就使用 refresh
。
信息
该逻辑通过 lint 规则自动强制执行。 如果尝试使用
ref.refresh
但并不使用返回的值,会有警告。
行为上的主要区别是在使 provider 失效之后紧接着读取它,provider 会立即重新计算。
不管在哪儿调用 invalidate
但是不接着读取的话,更新会在之后触发。
这个之后更新一般是下一个状态的开始。如果一个当前不被监听的 provider 是无效的,在下次被监听之前它是不会再次更新的。
为什么在 Ref 和 WidgetRef 之间没有共享接口?
Riverpod 是特意地将 Ref
和 WidgetRef
解耦。
其目的是避免编写其中一个条件性地依赖另外一个的代码。
一个 issue 是 Ref
和 WidgetRef
尽管看起来相似,但是还是有一些细微的差别。
依赖两者的代码会变得不可靠,并难以发现。
同时,依赖 WidgetRef
和依赖 BuildContext
是相同的。
事实上这会将业务逻辑放到 UI 层,并不推荐这样做。
这样的代码应该都使用 Ref
进行重构。
解决该问题的方案通常是将业务逻辑转移到 Notifier
(查看执行副作用)中,然后在 Notifier
的方法中编写业务逻辑。
用这种方法,当组件想调用业务逻辑时,可以写如下的代码行:
ref.read(yourNotifierProvider.notifier).yourMethod();
yourMethod
会使用 Notifier
的 Ref
和其它 provider 进行交互。
为什么要继承 ConsumerWidget 来代替原始的 StatelessWidget ?
这是因为 InheritedWidget
API 中的一个不合适的限制。
有这样一些问题:
-
使用
InheritedWidget
不可能实现 "on change" 监听。这意味着一些处理如ref.listen
不能使用BuildContext
。State.didChangeDependencies
是最接近的方式了,但是它是不可靠的。一个问题是即使没有依赖改变生命周期也能被触发,特别是如果组件树使用了 GlobalKey (并且一些 Flutter 组件内部就是这么做的)。 -
监听
InheritedWidget
的组件永远不会停止监听。对于纯粹的元数据,如 "theme" 或 "media query" 是没有问题的。但是对于业务逻辑,这是一个问题。比如说你在使用 provider 来表示一个分页 API 。当页面偏移位置改变时,你不会想着继续监听前面可见的页面。
-
InheritedWidget
在组件停止监听它们时没有追踪的方式。Riverpod 有时会依靠追踪一个 provider 是否在被监听。
该功能对自动清理机制和向 provider 传递参数的功能来说是至关重要的。 正是这些特性,才让 Riverpod 如此强大。
可能在遥远的将来,这些问题会被解决。这种情况下,Riverpod会迁移为使用 BuildContext
代替 Ref
。这会让使用 StatelessWidget
代替 ConsumerWidget
成为可能。
但是那是后话了!
为什么 hooks_riverpod 不导出 flutter_hooks ?
这是为了更好的版本控制实践。
离开 flutter_hooks
就不能使用 hooks_riverpod
,两个包都是独立进行版本控制的。
其中一个进行破坏性的改动时,就不会影响另一个。
为什么在一些场景中, Riverpod 使用 identical
代替 ==
过滤更新?
Notifier 使用 identical
代替 ==
过滤更新。
这是因为对于 Riverpod 用户来说,为了生成 copyWith 的实现,使用如 Freezed/built_value 之类的代码生成器是很常见的。这些包覆写 ==
用以深度比较对象。
深度比较对象成本很高。“业务逻辑”模型通常有很多属性。更糟的是,它们会有集合,如 List ,Map 等等。
同时,使用复杂的业务对象时,大多数 state = newState
调用都会导致通知(否则调用 Setter 就没有意义)。通常主要场景是在调用 state = newState
,对于基本类型对象,当前状态和新状态相同(int,枚举,但不是 List/类/。。)时。这些对象默认是可拆开的。如果这类对象相同,那一般它们也是同样的。
Riverpod 使用 identical
过滤更新是对于两边都有很好的默认处理。为了不需要真正关心对象过滤的复杂对象和默认覆写 ==
的代码生成器生成的 ==
可能带来的高成本。
使用 identical
提供了通知监听者的高效方式。同时,对于简单对象,identical
也会正确过滤冗余的通知。
最后但也并非不重要的是,可以通过覆写 Notifier 的 updateShouldNotify
改变其行为。
有没有一次性重置所有 provider 的方式?
没有,没有一次性重置所有 provider 的方式。
这是有意为之的,因为这被认为是反模式的。一次性重置所有的 provider 会导致将不想重置的 provider 也进行重置了。
想要在用户退出登录时重置应用状态时的开发者经常会问到这个问题。
如果这也是你后面要做的,那应该让依赖用户状态的所有内容都去 ref.watch
用户的 provider 。
然后,当用户退出登录时,所有依赖它的 provider 会、自动重置,但是其它内容会保持不变。
碰到了 "Cannot use "ref" after the widget was disposed" (无法在组件被清除后使用"ref")的错误,怎么回事?
也可能会看到 "Bad state: No ProviderScope found" (坏的状态:没有发现 ProviderScope ),同样问题的一个更早的错误信息。
该错误会在在不再加载的组件中尝试使用 ref
时会发生。
通常会发生在某个 await
之后:
ElevatedButton(
onPressed: () async {
await future;
ref.read(...); // 可能会抛出错误 "Cannot use "ref" after the widget was disposed"
}
)
解决方案是,如使用 BuildContext
,在使用 ref
之前检查 mounted
:
ElevatedButton(
onPressed: () async {
await future;
if (!context.mounted) return;
ref.read(...); // 不会再抛出错误
}
)
转载自:https://juejin.cn/post/7309921570879750181