Flutter 工程化框架选择 — 状态管理何去何从
这是 《Flutter 工程化框架选择》 系列的第六篇 ,就像之前说的,这个系列只是单纯告诉你,创建一个 Flutter 工程,或者说搭建一个 Flutter 工程脚手架,应该如何快速选择适合自己的功能模块,或者说这是一个指引系列,所以比较适合新手同学。
其实这是我最不想写的一个篇。
状态管理是 Flutter 里 ♾️ 的话题,本质上 Flutter 里的状态管理就是传递状态和基于 setState 的封装,状态管理框架解决的是如何更优雅地共享状态和调用 setState 。
那为什么我不是很想写状态管理的对比内容?
首先因为它很繁,繁体的煩,从 Flutter 发布到现在,scoped_model 、BLoC 、Provider 、 flutter_redux 、MobX、 fish_redux 、Riverpod 、GetX  等各类框架“百花齐放”,虽然这对于社区来说是这是好事,但是对于普通开发者来说很容易造成过度选择困难症,特别早期不少人被各种框架“伤害过”。
其次,状体管理在 Flutter 里一直是一个“敏感”话题,每次聊到状态管理就绕不开 GetX ,但是一旦聊  GetX  又会变成“立场”问题,所以一直以来我都不是很喜欢写状态管理的内容。
所以本来应该在第一篇就出现的内容,一直被拖到现在才放出来,这里提前声明一些,本篇不会像之前一样从大小和性能等方面去做对比,因为对于状态管理框架来说这没什么意义:
- 集成后对大小的影响可能还不如一张图片
- 性能主要取决于开发者的习惯,在状态管理框架上对比性能其实很主观
当然,如果你对集成后对大小的影响真的很在意,那可以在打包时通过 --analyze-size 来生成 analysis.json 文件用于对比分析:
flutter build apk --target-platform android-arm64 --analyze-size
上诉命令在执行之后,会在 /Users/你的用户名/.flutter-devtools/ 目录下生成一个 apk-code-size-analysis_01.json 文件,之后我们只需要打开 Flutter 的 DevTools   下的 App Size Tooling 就可以进行分析。
例如这里是将 Riverpod 和  GetX  在同一项项目集成后导出不同 json 在 Diff 进行对比,可以看到此时差异也就在 78.5kb ,这个差异大小还不如一张 png 资源图片的影响大。
所以本次主要是从这些状体管理框架自身的特点出发,简单列举它们的优劣,至于最后你觉得哪个适合你,那就见仁见智了~
本篇只是告诉你它们的特点和如何去选择,并不会深入详细讲解,如果对实现感兴趣的可以看以前分享过的文章:
Provider
2019 年的 Google I/O 大会 Provider 成了 Flutter 官方新推荐的状态管理方式之一,它的特点就是: 不复杂,好理解,代码量不大的情况下,可以方便组合和控制刷新颗粒度 , 其实一开始官方也有一个 flutter-provide ,不过后来宣告GG , Provider 成了它的替代品。
⚠️注意,
provider比flutter-provide多了个r,所以不要再看着 provide 说 Provider 被弃坑了。
简单来说,Provider 就是针对 InheritedWidget  的一个包装工具,他让 InheritedWidget 的使用变得更简单,在往下共享状态的同时,可以通过  ChangeNotifier 、 Stream 、Future  配合 Consumer* 组合出多样的更新模式。
所以使用 Provider 的好处之一就是简单,同时你可以通过  Consumer* 等来决定刷新的颗粒度,其实也就是 BuildContext  在 of(context) 时的颗粒度控制。
登记到
InheritedWidget里的 context 决定了更新是 rebuild 哪个ComponentElement,感兴趣的可以看 全面理解 State 与 Provider
当然,虽然一直说 Provider 简单,但是其实还是有一些稍微“复杂”的地方,例如 select 。
Provider 里 select  是对 BuildContext  做了 “二次登记” 的行为,就是以前你用 context 是 watch 的时候 ,是直接把这个 Widget 登记到 Element 里,有更新就通知。
但是  select   做了二次处理,就是用 dependOnInheritedElement 做了颗粒化的判断,如果是不等于了才更新,所以它对 context 有要求,如下图对就是对 context 类型进行了判断。
所以  select  算是  Provider  里的“小魔法“之一,总的来说 Provider 是一个符合 Flutter 的行为习惯,但是不大符合前端和原生的开发习惯的优秀状态管理框架。
优点:
- 简单好维护
- read、watch、select 提供更简洁的颗粒度管理
- 官方推荐
缺点:
- 相对依赖 Flutter 和 Widget
- 需要依赖 Context
最后顺带辟个谣,之前有 “传闻” Provider 要被弃坑的说法,作者针对这个也有相应对澄清,所以你还是可以继续安心使用 Provider。
Riverpod
Riverpod 和 Provider 是同个作者,因为 Provider 存在某些局限性,所以作者根据 Provider 这个单词重新排列组合成 Riverpod。
如果说 Provider  是 InheritedWidget 的封装,那 Riverpod  就是在 Provider 的基础上重构出更灵活的操作能力,最直观的就是 Riverpod 中的 Provider 可以随意写成全局,并且不依赖 BuildContext 来编写我们需要的业务逻。
注意: Riverpod 中的 Provider 和前面的 Provider 没有关系。
在 Riverpod 里基本是每一个 “Provider” 都会有一个自己的  “Element”  ,然后通过 WidgetRef 去 Hook 后成为 BuildContext 的替代,所以这就是 Riverpod 不依赖 Context 的 “魔法” 之一
⚠️这里的 “Element” 不是 Flutter 概念里三棵树的
Element,它是 Riverpod 里Ref对象的子类。Ref主要提供 Riverpod 内的 “Provider” 之间交互的接口,并且提供一些抽象的生命周期方法,所以它是 Riverpod 里的独有的 “Element” 单位。
另外对比 Provider ,Riverpod 不需要依赖 Flutter ,所以也不需要依赖 Widget,也就是不依赖 BuildContext ,所以可以支持全局变量定义  “Provider”  对象。
优点:
- 在 Provider 的基础上更加灵活的实现,
- 不依赖 BuildContext,所以业务逻辑也无需注入BuildContext
- Riverpod 会尽可能通过编译时安全来解决存在运行时异常问题
- 支持全局定义
- ProviderReference能更好解决嵌套代码
缺点:
- 实现更加复杂
- 学习成本提高
目前从我个人角度看,我觉得 Riverpod 时当前之下状态管理的最佳选择,它灵活且专注,体验上也更符合 Flutter 的开发习惯。
注意,很多人一开始只依赖
riverpod然后发现一些封装对象不存在,因为riverpod是不依赖 flutter 的实现,所以在 flutter 里使用时不要忘记要依赖flutter_riverpod。
BLoC
BLoC 算是 Flutter 早期比较知名的状态管理框架,它同样是存在 bloc 和 flutter_bloc 这样的依赖关系,它是基于事件驱动来实现的状态管理。
flutter_bloc  基于事件驱动的核心就是 Stream 和  Provider , 是的, flutter_bloc   依赖于 Provider,然后在其基础上设计了基于  Stream  的事件响应机制。
所以严格意义上 BLoC 其实是 Provider +   Stream   ,如果你一直很习惯基于事件流开发模式,那么 BLoC 就很适合你,但是其实从我个人体验上看,BLoC 在开发节奏上并不是快,相反还有点麻烦,不过优势也很明显,基于 Stream    的封装可以更方便做一些事件状态的监听和转换。
BlocSelector(
selector: (state) {
// return selected state based on the provided state.
  },
builder: (context, state) {
// return widget here based on the selected state.
  },
)
MultiBlocListener(
listeners: [
    BlocListener(
listener: (context, state) {},
    ),
    BlocListener(
listener: (context, state) {},
    ),
    BlocListener(
listener: (context, state) {},
    ),
  ],
child: ChildA(),
),>,>,>,>优点:
- 代码更加解耦,这是事件驱动的特性
- 把状态更新和事件绑定,可以灵活得实现状态拦截,重试甚至撤回
缺点:
- 需要写更多的代码,开发节奏会有点影响
- 接收代码的新维护人员,缺乏有效文档时容易陷入对着事件和业务蒙圈
- 项目后期事件容易混乱交织
类似的库还有 rx_bloc ,同样是基于
Stream和 Provider , 不过它采用了 rxdart 的Stream封装。
flutter_redux
flutter_redux 虽然也是 pub 上的 Flutter Favorite 的项目,但是现在的 Flutter 开发者应该都不怎么使用它,而恰好我在刚使用 Flutter 时使用的状态管理框架就是它。
其实前端开始者对 redux 可能会更熟悉一些,当时我恰好用 RN 项目切换到 Flutter 项目,在 RN 时代我就一直在使用 redux,flutter_redux 自然就成了我首选的状态管理框架。
其实这也是 Flutter 最有意思的,很多前端的状态管理框架都可以迁移到 Flutter ,例如 flutter_redux  里就是利用了 Stream特性,通过 redux 单向事件流的设计模式来完成解耦和拓展。
在 flutter_redux  中,开发者的每个操作都只是一个 Action ,而这个行为所触发的逻辑完全由 middleware 和 reducer 决定,这样的设计在一定程度上将业务与UI隔离,同时也统一了状态的管理。
当然缺陷也很明显,你要写一堆代码,开发逻辑一定程度上也不大符合 Flutter 的开发习惯。
优点:
- 解耦
- 对 redux 开发友好
- 适合中大型项目里协作开发
缺点:
- 影响开发速度,要写一堆模版
- 不是很贴合 Flutter 开发思路
说到 redux 就不得不说 fish_redux ,如果说 redux 是搭积木,那闲鱼最早开源的  fish_redux 可以说是积木界的乐高,闲鱼在 redux 的基础上提出了 Comoponent 的概念,这个概念下 fish_redux 是从 Context 、Widget 等地方就开始全面“入侵”你的代码,从而带来“超级赛亚人”版的 redux 。
所以不管是 flutter_redux 还是 fish_redux 都是很适合团队协作的开发框架,但是它的开发体验和开发过程,注定不是很友好。
GetX
GetX 可以说是 Flutter 界内大名鼎鼎,Flutter 不能没有 GetX 就像程序员不能没有 PHP ,GetX 很好用,很具备话题,很全面同时也很 GetX。
严格意义上说现在 GetX 已经不是一个简单的状态管理框架,它是一个统一的 Flutter 开发脚手架,在 GetX 内你可以找到:
- 状态管理
- 路由管理
- 多语言支持
- 页面托管
- Http  GetConnect
- Rx GetStream
- 各式各样的 extension
可以说大部分你想到的 GetX 里都有,甚至还有基于 GetX 的 get_storage 实现纯 Dart 文件级 key-value 存储支持。
所以很多时候使用 GetX 开发甚至不需要关心 Flutter ,当然这也导致经常遇到的奇怪情况:大家的问题集中在 GetX 里如何 xxxx,而不是 Flutter 如何 xxxx ,所以 GetX 更像是依附在 Flutter 上的解决方案。
当然,使用 GetX 最直观的就是不需要   BuildContext  ,甚至是你在路由跳转时都不需要关心 Context ,这就让你的代码看起来很“干净”,把整个开发过程做到“面向 GetX 开发”的效果 。
另外 GetX 和 Provider 等相比还具备的特色是:
- Get.put、- Get.find、- Get.to等操作完全无需 Widget 介入
- 内置的 extension如各类基础类似的*.obs通过GetStream实现了如var count = 0.obs;和Obx(() => Text("${controller.name}"));这样的简化绑定操作
那 GetX 是如何脱离 Context 的依赖?说起来也不复杂,例如 :
- GetMaterialApp内通过一个会有一个- GlobalKey用于配置- MaterialApp的- navigatorKey,这样就可以通过全局的- navigatorKey获取到- Navigator的- State,从而调用- pushAPI 打开路由
- Get.put和- Get.find是通过一个内部全局的静态- Map来管理,所以在传递和存放时就脱离了- InheritedWidget,结合- Obx,在对获取到的- GetxController的 value 时会有个- addListener的操作,从而实现- Stream的绑定和更新
可以说 GetX 内部有很多“魔法”,这些魔法或者是对 Flutter API 的 Hook、或者是直接脱离 Flutter 设计的自定义实现,总的来说 GetX “有自己的想法”。
这也就带来一个了个问题,很多人新手一上手就是 GetX ,然后对 Flutter 一知半解,特别是深度解绑了 Context 之后,很多 Flutter 问题就变成了 GetX 上如何 xxxx,例如前面的: Flutter GetX 如何调用谷歌地图这种问题。
如果使用 GetX 而不去思考和理解 GetX 的实现,就很容易在 Flutter 的路上走歪,比如上面各种很基础的问题。
这其实也是 GetX 的最大问题:GetX 做的很多,它入侵到很多领域,而且它拥有很多“魔法”,这些“魔法”让 Flutter 开发者不知布局的脱离了本来应有的轨迹。
当然,你说我就是想完成需求,好用就行,何必关心它们的实现呢?从这个角度看 GetX 无疑是非常不错的选择,只要 GetX 能继续维护下去并把“魔法”继续兼容。
大概就是:GetX “王国” 对初级开发者友好,但是“魔法全家桶”其实对社区的健康发展很致命。
优点:
- 瑞士军刀式护航
- 对新人友好
- 可以减少很多代码
缺点:
- 全家桶,做的太多对于一些使用者来说是致命缺点,需要解决的 Bug 也多
- “魔法”使用较多,脱离 Flutter 原本轨迹
- 入侵性极强
总的来说,GetX 很优秀,他帮你都写好了很多东西,省去了开发者还要考虑如何去组合和思考的过程,从我个人的角度我不喜欢这种风格,但是它总归是可以帮助你提高开发效率。
另外还有一个状态管理库 Mobx ,它库采用了和 GetX 类似的风格,虽然 Mobx 的知名度和关注度不像 GetX 那么高,但是它同样采用了隐式依赖的模式,某种意义上可以把 Mobx 看成是只有状态管理版本的 GetX。
最后
通过上面分享的内容,相信大家对于选哪个状态管理框架应该有自己的理解了,还是那句废话,采用什么方案和框架具体还是取决于你的需求场景,不管是哪个框架目前都有坑和局限,重点还是在于它未来是否持续维护,或者不维护了你自己能否继续维护下去。
链接:https://juejin.cn/post/7163925807893577735
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 
			