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