[译]让我来帮你理解和选择Flutter状态管理方案

11,357 阅读6分钟

This article is from Medium written by Jorge Coca, Thank you Jorge for allowing me translate your awesome article into Chinese

本文来源于Medium,由Jorge Coca撰写,并准许我翻译成中文

状态管理在Flutter中是一个很热的话题。可选的方案有很多,这可能很好,但却很容易陷入其中,在项目中选择最适合方案时感到迷失。我也是,不过我已经找到了适合我的方案,让我来分享给你。

分享时刻

为了找到适合需求的方案,头一件事就是确认需求,然后设置目标和期望。对我而言,我定义了如下:

  • 允许稳定的开发速度,而不牺牲代码质量
  • 分离展示逻辑和业务逻辑
  • 容易理解;难以破坏
  • 可预期并且可以广泛部署

在给定了这些限制后,我们来看看我们可选的方案:

  • 使用StatefulWidgets的setState()
  • ScopedModel
  • BLoC(Business Logic Component)
  • Redux

理解局部状态和全局状态的不同

在深入分析不同方案前,有一件事可能会帮助我们更好的理解怎样选择——什么是局部状态,什么是全局状态

为了这件事,我们来考虑一件事:想象一个简单的登陆表单,用户可以输入用户名和密码,如果登陆成功,就可以从后台获得到id。在这个例子中,登录表单的任何验证类型,都可以考虑为局部状态,因为这些规则仅适用于这个组件,而App的其他部分不需要知道这个类型。但是从后台获取的id,就需要考虑成全局状态,因为它影响整个app的作用域(未登录和已登陆),而且可能别的组件会依赖它。

简而言之:告诉我结果

如果你不想等太久,或者对深入探索不感兴趣,下面这个表可以快速告诉你我的发现:

我的建议是:使用BLoC进行局部状态管理,使用Redux进行全局状态管理。特别是对持续迭代的复杂应用而言。

为什么不用setState()?

快速制作原型的时候,在widget中使用setState()非常爽,而且你会立即得到反馈。但是这并不能帮助我们实现目标:展示逻辑和业务逻辑在同一个类里,打破了干净和高质量代码的原则。以后应用的规模扩张的时候,代码的维护将会是一个挑战。因此除了快速原型设计,我不建议使用setState()。

ScopedModel,正确方向上的一步

ScopedModel是Brian Egan维护的第三方包。它让我们创建Model对象,然后在我们需要的时候调用notifyListeners();例如,在我们的模型(model)属性改变的时候:

class CounterModel extends Model {
  int _counter = 0;
  int get counter = _counter;
  void increment() {
    _counter++;
    notifyListeners();
  }
}

在我们的widget中,我们可以使用ScopedModelDescendant来响应模型的变化:

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,就可以分离展示逻辑和业务逻辑,这样就很好了。但是也会有一些局限:

  • 如果你的模型变得复杂,挑选调用notifyListeners()的时机以防止没必要的更新,将会是一个挑战。
  • Model的API并没有准确描述UI应用程序的异步性。

综上,除非状态很容易控制,否则我不建议你使用ScopedModel;如果你的应用足够复杂,我不相信这是支持复杂性和迭代的正确答案。

BLoC,一个强大的方案

BLoC是一个被Google创造并使用的设计模式;他会帮助我们完成这几件事:

  • 分离展示逻辑和业务逻辑
  • 拥抱UI应用的异步性
  • 可以在不同的dart应用中复用,不论是Flutter应用还是Angular dart应用

BLoC背后的想法很简单:

  • BLoC开放Sink<I>API来描述我们组件的异步输入
  • BLoC开放Stream<T>API来描述我们组件的异步输出
  • 最终,我们使用StreamBuilderwidget来管理数据流,我们不需要再维护对数据流的订阅和widget的重绘

因为它被Google重度使用和推荐,他们有个非常好的例子:github.com/filiph/stat…


我非常推荐你在应用中使用BLoC,特别是管理局部状态。

即使是全局状态管理,我相信它也会有很好的解决;但是你将会在这个领域面临一些挑战,例如怎么在不同UI组件中插入BLoC更合适,我想这是Redux的闪光点。

Redux和BLoC,对我来说完美的搭配

在文章的开头,其中一个目标就是找到一个可以广泛应用并且可预期的方案。好吧,就是Redux。

Redux是一个设计模式,融合了一些工具,来帮助我们管理全局状态。它建立在3个基本原则上:

  • 单一事实来源:你的整个应用的状态,都存在在单一的store中,并且以一个树状对象存在
  • 状态是只读的:改变状态的唯一方法就是发射action,一个描述发生了什么的对象
  • 改变被纯函数处理:为了说明action怎么改变状态,你想要写一个纯函数的reducer(译者注:纯函数就是这个函数内部不存储状态,在相同的参数的前提下,无论在何时调用,返回值严格一致)

Redux是网页开发着广泛使用的设计模式(译者注:比如用在React.js中),所以在移动端也有广泛的基础,会让我们互相收益。

Brian Egan维护redux和flutter_redux,而且创造了一个非常棒的ToDo 应用, 集成了很多不同的架构模式,包括Redux。

鉴于Redux的所有特性,我完全建议使用它来管理全局状态,但是如果你想扩张你的应用,请确保在Redux架构里面不包含局部状态。

最后的思考

这里并没有正确与错误答案。为了选一个工具,或者应用一个设计模式,理解你自己的需求非常重要。对我和我的需求来说,Redux和BLoC搭配能帮助我快速安全地扩张我的应用。同时因为工具的可用性被社区很好地理解,更多的开发者开始使用它。话说回来,每个人都有不同的需求和看问题的角度,重要的是始终有好奇心,去学习和思考,什么才是最好的方案

你可以访问原作者的GitHub

也欢迎关注我的更多文章,以及我的GitHub