本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
这是 《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 资源图片的影响大。
所以本次首要是从这些状体办理结构自身的特色动身,简略罗列它们的优劣,至于最后你觉得哪个合适你,那就见仁见智了~
本篇仅仅告知你它们的特色和怎么去挑选,并不会深入详细解说,假如对完成感兴趣的能够看曾经共享过的文章:
Flutter Riverpod 全面深入解析
全面了解 State 与 Provider
全面深入了解状况办理规划
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<BlocA, BlocAState, SelectedState>(
selector: (state) {
// return selected state based on the provided state.
},
builder: (context, state) {
// return widget here based on the selected state.
},
)
MultiBlocListener(
listeners: [
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
),
BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
),
BlocListener<BlocC, BlocCState>(
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
,从而调用push
API 翻开路由 -
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。
最后
经过上面共享的内容,相信我们关于选哪个状况办理结构应该有自己的了解了,仍是那句废话,选用什么计划和结构详细仍是取决于你的需求场景,不管是哪个结构目前都有坑和限制,要点仍是在于它未来是否持续保护,或许不保护了你自己能否持续保护下去。
最后,假如你还有什么疑问,或许针对 Flutter 工程挑选上还有哪些茫然,欢迎留言谈论。