Flutter 中的状态管理

标签: 编程学习 Flutter学习

状态管理是前端开发中很常见的一个概念,有幸在几年前写过一点点 React,当时我们用的是 dva 也就是一种 redux 的衍生解决方案,当时的感觉是前端终于成为应用开发了,不再像很久之前做的网页那样繁琐(虽然我现在还在用传统方式开发网站😁)。 而在 Flutter 的开发中很像前端的开发,直接就有类似的操作,所以我们也要来研究一下这些问题。

为什么要引入状态管理

  • 随着应用的规模越来越大,功能越来越复杂,组件的抽象粒度会越来越细,在视图中组合起来后层级也会越来越深,能够方便的跨组件共享状态成为迫切的需求。
  • 状态也需要按模块切分,状态的变更逻辑背后其实就是我们的业务逻辑,将其抽离出来能够彻底解耦ui和业务,有利于逻辑复用,以及持续的维护和迭代。
  • 状态如果能够被集中的管理起来,并合理的派发有利于组件按需更新,缩小渲染范围,从而提高渲染性能。

Flutter 中的状态管理

Flutter 中有很多状态管理方式,下面简单列举一些:

  • StatefulWidget 中的 state
  • InheritedWidget
  • Provider
  • BLoC
  • Redux

其实分类的界限也并没有那么的清晰,这里大体分类两类

  • UI 状态管理, 有时也叫UI state或local state,这种可以包含在单个widget里。
  • App 状态管理,需要在很多地方共享的状态, 也叫shared state或global state。

状态管理方法

StatefulWidget

大家都知道 Flutter 中一切皆 Widget,而我们用的 Widget 有两种:StatelessWidget 和 StatefulWidget,从名字都可以看出来前者是无状态的组件,后者是有状态的组件。

对于一些简单的应用或者说简单的页面,当我们只是做一些静态的不需要操作更新数据的页面的时候,用到的是 StatelessWidget,对于有更新操作的页面用到的是 StatefulWidget, 就像新创建的 Flutter 项目里面包含的例子,改变数值之后只需 setState 就可以刷新页面。

而这个过程前面我也介绍过,就是将当前的 Element 标记为 _dirty,等到下一帧的时候重新 build 当前 State 中 Element。

对于复杂的状态, 这种方式的缺点:

  1. 状态属性多了以后, 可能有很多地方都在调用 setState()。(请求多的时候到处都要setState)
  2. 不能把状态和UI分开管理。
  3. 不利于跨组件/跨页面的状态共享。 (比如我们项目中有一个小模块兄弟组件之间调用 setState 是使用的 key 传递,但是跨页面就比较坑了)

这样就会导致很多问题,比如 耦合度高,要全局保存的state,从外部调用setState方法。

InheritedWidget

InheritedWidget 在前面我也专门介绍过,它主要作用是在 Widget 树中有效地传递信息,Flutter 中常用的 Theme, Style, MediaQuery 等就是inherited widget, 所以在程序里的各种地方都可以访问到它们。

InheritedWidget解决了访问状态和根据状态更新的问题, 但是改变 state 却不太好

  • 不支持跨页面(route)的状态, 因为widget树变了, 所以需要进行跨页面的数据传递。
  • InheritedWidget它包含的数据是不可变的, 如果想让它跟踪变化的数据就要把它包在一个StatefulWidget里,或者在 InheritedWidget 中使用 ValueNotifier, ChangeNotifier 或 steams。

Provider

Provider 前面我也详细介绍了,是目前官方推荐的工具,Provider的实现在内部还是利用了 InheritedWidget。dispose 指定后会自动被调用, 支持 MultiProvider。 Provider 性能相关的实现细节

  • Consumer包裹的范围要尽量小。
  • listen变量。
  • child的处理。
return Consumer<TestModel>(
  builder: (context, test, child) => Stack(
        children: [
          // SomeWidget 用到这里,不会每次都 rebuild
          child,
          Text("Total price: ${test.title}"),
        ],
      ),
  child: SomeWidget(),
);

BLoC

BLoC 前面我也有介,侧重于利用流(Stream) 来构成的异步世界,

Bloc -> Stream -> Widgets

Redux

Redux 是前端流行的, 一种单向数据流架构。 里面锁包含的概念: Store: 用于存储State对象, 代表整个应用的状态. Action: 事件操作. Reducer: 用于处理和分发事件的方法, 根据收到的 Action, 用一个新的 State 来更新 Store. View: 每次 Store 接到新的 State, View 就会重建. Reducer是唯一的逻辑处理部分, 它的输入是当前 State 和 Action, 输出是一个新的 State.

之前看过咸鱼的 fish-redux,也用它写过小项目,感觉拆的太多,写起来太过复杂,所以一直也没有采用。。