Flutter 中的状态管理是一个热门话题。为了选择适合需求的解决方案,实际上要做的第一件事就是明确这些需求,并设定一些目标。基于自身需求,我确定了这些:

  • 在不牺牲代码质量的情况下实现稳定的开发速度
  • 将展示逻辑与业务逻辑分开
  • 便于理解;结构层次明确。
  • 具备普适性,满足绝大部分场景

考虑到这些限制,我们将探索以下选项:

  • 使用 setState()StatefulWidgets
  • ScopedModel 范围模型
  • BLoC (Business Logic Component)
  • Redux

了解局部状态和全局状态之间的差异

在深入探讨各种状态管理方案之前,理解什么是本地状态和全局状态将有助于我们更好地做出选择。为此,让我们来看一个实际例子:

为了更好地理解,让我们看一个实际例子:想象一个简单的登录表单,用户可以在其中输入用户名和密码,如果数据匹配,则从后端获取 “用户” 对象。在这个例子中,任何类型的登录表单验证都可以被视为局部状态,因为这些规则的范围只适用于这个组件,而我们应用程序的其他部分并不需要知道它。然而,从后端获得的 “用户” 对象可以被视为我们全局状态的一部分,因为它改变了整个应用程序的范围(未认证 vs. 认证),并且可能其他组件也依赖于它的数据。

我的方案

我的建议是使用 BLoC 模式进行本地状态管理,并依靠 Redux 来支持全局状态范围。特别是,如果这些是会随着时间的推移而增长的复杂应用程序。

为什么不使用 setState()

使用Widget内部的 setState() 开发很便利,可以获得即时反馈。然而,它并不能帮助我们实现目标:表示层和业务逻辑都属于同一个类,违反了代码整洁和质量的原则。随着应用程序的增长,代码维护将来可能会成为一个挑战,

ScopedModel,朝着正确方向迈出的一步

ScopedModel 是由 Brian Egan 维护的第三方包。它允许我们创建 Model 对象,并在我们认为有必要时使用名为 notifyListeners() 的方法;例如,我们模型中的任何属性更改。

1
2
3
4
5
6
7
8
class CounterModel extends Model {
int _counter = 0;
int get counter = _counter;
void increment() {
_counter++;
notifyListeners();
}
}

在我们的Widget中,我们可以使用 ScopedModelDescendant 对模型中的更改做出反应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CounterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ScopedModel<CounterModel>(
model: new CounterModel(),
child: new Column(children: [
new ScopedModelDescendant<CounterModel>(
builder: (context, child, model) => new Text('${model.counter}'),
),
new Text("Another widget that doesn't depend on the CounterModel")
])
);
}
}

setState() 相反,如果我们使用 ScopedModel 我们可以将业务逻辑与表示逻辑分开。然而,它存在一些限制:

  • 如果你的 Model 很复杂,那么了解何时正确调用 notifyListeners() 以避免不必要的更新可能会很困难。
  • 公开的 API 并未准确描述一般 UI 应用程序的异步性质。

有了所有这些信息,我不建议使用 ScopedModel ,除非您的状态非常容易管理;如果您的应用程序足够复杂,我不认为这是支持这种复杂性和增长的正确答案。

BLoC,强大的解决方案

BLoC 是 Google 推出并使用的一种模式;它将帮助我们实现以下目标:

  • 将业务逻辑与表示逻辑分开
  • 支持UI应用的异步特性
  • 可以在不同的 Dart 应用中重用,无论是 Flutter 应用还是 Angular Dart 应用。

BLoC 实现的思路非常简单:

  • BLoC 公开 Sink<I> API 来描述组件的异步输入
  • BLoC 公开 Stream<T> API 来描述组件的异步输出
  • 最后,我们可以使用 StreamBuilder widget来管理数据流,从而消除了维护流订阅和重绘Widget树的工作。

由于这是 Google 内部大量使用并推荐的方式,因此他们有非常好的示例,例如:https://github.com/filiph/state_experiments/tree/master/shared/lib/src/bloc_complex

建议在你的应用程序中使用 BLoC,特别是管理本地状态。即使对于全局状态管理,我相信这也可以是一个很好的解决方案;然而,您可能在这方面面临一些挑战,例如,在何处用正确的注入由不同 UI 组件访问的 BLoC,而这正是像 Redux 这样的解决方案的优势所在。

Redux 和 BLoC,完美的组合

我在文章开头描述的目标之一是寻找被广泛采用和可预测的东西。嗯,这就是 Redux。

Redux 是一种模式,结合一些工具,将帮助我们管理全局状态。它基于三个核心原则构建::

  • 单一事实来源:整个应用程序的 state 存储在单个 store 内的对象树中。
  • 状态是只读的:更改状态的唯一方法是发出 action,一个描述所发生情况的对象,即action。
  • 纯函数修改状态: 为了指定状态如何被 action 转换,您需要编写纯函数reducer。

alt text

基于 Redux 的所有优点,推荐将其用于管理全局状态。但是,如果你希望应用可扩展,请确保您的 Redux 架构中没有引入任何局部状态更改。

最后的想法

在选择管理状态的工具或模式时,没有绝对正确或错误的答案。最重要的是理解你的需求,选择最适合你的方案。

就我而言,结合 BLoC 和 Redux 的方式能够快速安全地扩展我的项目,同时由于这些工具在社区中广为人知,也能方便其他开发者的加入。然而,每个人的需求都不同,未来也可能会遇到问题或出现更好的工具。因此,保持好奇心,不断学习并思考什么是最适合你的解决方案才是最重要的。