riverpod-faq-and-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Riverpod — FAQ and best practices

Riverpod — 常见问题与最佳实践

FAQ

常见问题

ref.refresh vs ref.invalidate

ref.refresh 与 ref.invalidate

ref.refresh = ref.invalidate + ref.read: it invalidates the provider and returns the new value. Use invalidate when you don't need the new value (e.g. "recompute when the user pulls to refresh"). Use refresh when you need the result right away. After invalidate alone, recomputation can happen at the next frame or when the provider is next read; refresh forces immediate recomputation by reading.
ref.refresh = ref.invalidate + ref.read:它会使provider失效并返回新值。当你不需要新值时使用invalidate(例如「用户下拉刷新时重新计算」)。当你需要立即获取结果时使用refresh。单独调用invalidate后,重新计算可能会在下一帧或下次读取provider时发生;refresh会通过读取强制立即重新计算。

Why no shared interface between Ref and WidgetRef?

为什么Ref与WidgetRef没有共享接口?

Ref (in providers) and WidgetRef (in widgets) are kept separate on purpose so you don't write code that conditionally depends on both; they have subtle differences and mixing would be error-prone. Prefer Ref: put logic in a Notifier and call ref.read(notifierProvider.notifier).yourMethod() from the UI; the method uses the Notifier's Ref.
Ref(用于providers中)和WidgetRef(用于组件中)被刻意分开,目的是避免你编写同时依赖两者的条件代码;它们存在细微差异,混用容易出错。优先使用Ref:将逻辑放在Notifier中,然后从UI调用ref.read(notifierProvider.notifier).yourMethod();该方法会使用Notifier的Ref。

Why ConsumerWidget instead of StatelessWidget?

为什么使用ConsumerWidget而非StatelessWidget?

InheritedWidget (and thus BuildContext) cannot support "on change" listeners like ref.listen, cannot know when widgets stop listening (needed for auto-dispose and family), and has lifecycle quirks (e.g. with GlobalKeys). Riverpod needs a Ref that isn't tied to BuildContext for these features. Hence ConsumerWidget (and Consumer) provide a Ref.
InheritedWidget(以及BuildContext)无法支持ref.listen这类「变更监听」功能,也无法知晓组件何时停止监听(这是自动销毁和family功能所需的),还存在生命周期问题(例如使用GlobalKeys时)。Riverpod需要一个不依赖BuildContext的Ref来实现这些特性。因此ConsumerWidget(和Consumer)提供了Ref。

Why doesn't hooks_riverpod export flutter_hooks?

为什么hooks_riverpod不导出flutter_hooks?

So each package can be versioned independently; a breaking change in one doesn't force the other to break.
这样每个包可以独立版本化;其中一个包的破坏性变更不会强制另一个包也发生变更。

Reset all providers at once?

能否一次性重置所有providers?

There is no API to reset all providers; it's considered an anti-pattern. For "logout and clear state", have providers that depend on the current user ref.watch a user provider; when the user logs out, that provider changes and dependents recompute or clear. Only user-dependent state resets; the rest stays.
没有重置所有providers的API;这被视为反模式。对于「登出并清除状态」的场景,可让依赖当前用户的providers通过ref.watch监听用户provider;当用户登出时,该用户provider发生变化,依赖它的providers会重新计算或清除状态。仅重置与用户相关的状态,其余状态保持不变。

"Using ref when a widget is about to or has been unmounted is unsafe"

「在组件即将卸载或已卸载时使用ref是不安全的」

This (or "No ProviderScope found") happens when ref is used after an await in a widget that may have been disposed. After any await, check if (!context.mounted) return; before using ref (same pattern as with BuildContext).

此提示(或「未找到ProviderScope」)发生在组件可能已销毁后,在await之后使用ref的场景。在任何await之后,使用ref前需检查if (!context.mounted) return;(与BuildContext的使用模式相同)。

Do / Don't

注意事项

AVOID initializing providers in a widget

避免在组件中初始化providers

Providers should initialize themselves. Don't call something like ref.read(provider).init() from initState. If initialization depends on user action (e.g. navigation), trigger it from the action (e.g. onPressed before Navigator.push).
Providers应自行完成初始化。不要在initState中调用类似ref.read(provider).init()的代码。如果初始化依赖用户操作(例如导航),请从该操作触发(例如在Navigator.push前的onPressed中执行)。

AVOID using providers for ephemeral state

避免使用providers存储临时状态

Use providers for shared business state, not for: selected tab/item, form state (which should reset on leave/back), animations, or controller-like state (e.g. TextEditingController). For local widget state use flutter_hooks or local state. Storing "selected item" in a global provider can break back navigation (e.g. back from /books/21 should show /books/42, but the provider still holds 21).
Providers应用于共享业务状态,而非以下场景:选中的标签/项、表单状态(离开/返回时应重置)、动画或控制器类状态(例如TextEditingController)。组件本地状态请使用flutter_hooks或本地状态。将「选中项」存储在全局provider中可能会破坏返回导航(例如从/books/21返回后应显示/books/42,但provider仍保留21)。

DON'T perform side effects during provider initialization

不要在provider初始化时执行副作用

Providers should represent "read" operations. Don't use a provider's build to perform "write" operations (e.g. submitting a form). That can lead to skipped or duplicated side effects. For loading/error of a side effect use mutations (see riverpod-mutations).
Providers应代表「读取」操作。不要在provider的build方法中执行「写入」操作(例如提交表单)。这可能导致副作用被跳过或重复执行。如需处理副作用的加载/错误状态,请使用mutations(参见riverpod-mutations)。

PREFER statically known providers with ref.watch/read/listen

优先使用静态已知的providers配合ref.watch/read/listen

Use ref.watch(provider) where provider is a top-level final (or otherwise statically known). Avoid passing a provider as a parameter and then watching it so that static analysis and riverpod_lint can work.
provider是顶级final(或其他静态已知)变量的场景下,使用ref.watch(provider)。避免将provider作为参数传递后再监听它,这样静态分析和riverpod_lint才能正常工作。

AVOID dynamically creating providers

避免动态创建providers

Define providers as top-level final variables. Don't create them inside classes or as instance fields; that can cause memory leaks and unsupported behavior. Static final in a class is allowed but not supported by code generation.
Enable riverpod_lint (see riverpod-getting-started) to enforce many of these practices.
将providers定义为顶级final变量。不要在类内部或作为实例字段创建它们;这可能导致内存泄漏和不支持的行为。类中的静态final变量是允许的,但不受代码生成支持。
启用riverpod_lint(参见riverpod-getting-started)可强制遵循上述多项规范。