为了消除 BLoC 是 前进道路的神话:没有完美的处理状态的方法。
每个状态管理架构都比其他的更好地解决了一些问题;总有权衡取舍,在决定架构时了解它们很重要。
通常,好的架构是实用的:它具有可扩展性和可扩展性,同时只需要最少的开销。
由于人们对实用性的看法不同,架构总是涉及到意见,所以下面我将就如何为您的应用采用 BLoC 阐述我个人的看法。
BLoC 是 Flutter 中一种非常有前途的状态管理方法,因为它有一个标志性成分:流。
它们允许将 UI 与业务逻辑分离,并且它们与 Flutter-ish 的方法很好地配合,一旦它们过时就重建整个小部件子树。
所以很自然,BLoC 之间的每次通信都应该使用流,对吗?
+----+ Stream +------+
| UI | --------> | BLoC |
| | <-------- | |
+----+ Stream +------+
嗯,有点。
要记住的重要一点是状态管理架构是达到目的的手段;你不应该只是为了它而做事,而是要保持开放的心态,仔细评估每个选项的利弊。
我们将 BLoC 与 UI 分开的原因是 BLoC 不需要关心 UI 的结构——它只提供一些不错的简单流,而数据发生的任何事情都是 UI 的责任。
虽然流已被证明是一种将信息从 BLoC 传输到 UI 的绝妙方式,但它们在另一个方向上增加了不必要的开销:
Streams 旨在传输连续的数据流(甚至在名称中也是如此),但大多数时候,UI 只需要触发 BLoC 中的单个事件。这就是为什么有时您会看到一些 Stream<void>s 或类似的 hacky 解决方案¹,只是为了遵守严格的 BLoC-y 做事方式。
此外,如果我们要基于来自 BLoC 的流推送新路由,则 BLoC 基本上会控制 UI 流——但我们试图阻止的正是直接控制 UI 和业务逻辑的代码!
这就是为什么一些开发人员(包括我)只是打破了完全基于流的解决方案并采用自定义方式从 UI 触发 BLoC 中的事件。
就个人而言,我只是使用方法调用(通常返回 Futures)来触发 BLoC 的事件:
+----+ method calls +------+
| UI | ----------------> | BLoC |
| | <---------------- | |
+----+ Stream, Future +------+
在这里,BLoC 为“实时”数据返回 Streams,并为方法调用返回 Futures。
让我们看看这对您的示例有何影响:
- BLoC 可以提供用户是否登录的
Stream<bool>,甚至可以提供Stream<Account>,其中Account 包含用户的帐户信息。
- BLoC 还可以提供异步
Future<void> signIn(String username, String password) 方法,如果登录成功则不返回任何内容,否则会抛出错误。
- UI 可以自行处理输入管理,并在按下登录按钮后触发类似以下内容:
try {
setState(() => _isLoading = true); // This could display a loading spinner of sorts.
await Bloc.of(context).signIn(_usernameController.text, _passwordController.text);
Navigator.of(context).pushReplacement(...); // Push logged in screen.
} catch (e) {
setState(() => _isLoading = false);
// TODO: Display the error on the screen.
}
这样,您可以很好地分离关注点:
- BLoC 确实只是在做它应该做的事情——处理业务逻辑(在本例中,是让用户登录)。
- UI 只关心两件事:
- 显示来自
Streams 和的用户数据
- 通过在 BLoC 中触发用户操作并根据结果执行 UI 操作来对用户操作做出反应。²
最后,我想指出,这只是一种可能的解决方案,它随着时间的推移在复杂应用程序中尝试不同的状态处理方式而演变。
了解关于状态管理如何工作的不同观点非常重要,因此我鼓励您深入研究该主题,也许可以通过观看 Google I/O 上的"Pragmatic State Management in Flutter" 会议。
编辑:刚刚在Brian Egan's architecture samples 中发现了这个架构,它被称为“Simple BLoC”。如果你想了解不同的架构,我真的建议你看看 repo。
¹当尝试为 BLoC 操作提供多个参数时,它变得更加丑陋 - 因为你需要定义一个包装类,只是为了将它传递给 Stream。
² 我确实承认在启动应用程序时它有点难看:您需要某种启动屏幕来检查 BLoC 的流并根据以下内容将用户重定向到适当的屏幕无论他们是否登录。发生该规则的异常是因为用户执行了一个操作——启动应用程序——但 Flutter 框架并不允许我们直接挂钩(据我所知,至少不是很优雅)。