2-Flutter 状态管理框架
声明式 UI
声明式有哪些优势,并带来了哪些问题呢?
优势: 让开发者摆脱组件的繁琐控制,聚焦于状态处理
习惯 Flutter 开发之后,回到原生平台开发,你会发现当多个组件之间相互关联时,对于 View 的控制非常麻烦。
而在 Flutter 中我们只需要处理好状态即可 (复杂度转移到了状态 -> UI 的映射,也就是 Widget 的构建)。包括 Jetpack Compose、Swift 等技术的最新发展,也是在朝着「声明式」的方向演进。
声明式开发带来的问题
没有使用状态管理,直接「声明式」开发的时候,遇到的问题总结有三个:
逻辑和 UI 耦合 ✅
直接在setState的组件里写逻辑,确实会造成难以复用、测试的问题。
所以才有 Provider、Riverpod、Bloc 等框架,把逻辑和 UI 解耦。难以跨组件访问数据 ✅
Flutter 的 widget 是树形结构,默认数据传递只能一级一级传(父传子)。
如果层级很深,就会很麻烦,这就是“状态提升”问题。
所以才有 InheritedWidget / Provider / Riverpod。无法轻松控制刷新范围 ❌(这个表述有点偏)
- setState 的确只能以 widget 为单位刷新,刷新的是“当前组件 + 子树”。
- 它不会全局刷新(不会动到整个页面)。
- 真正的问题是:如果你的 widget 太大,一个很小的状态变化就会导致整块 widget rebuild,性能会浪费。
- 状态管理框架能帮你把数据拆分、精准刷新,不需要重建大块 UI。
所以要使用状态管理框架
setState
setState的刷新范围,不是全局刷新。
setState 只会触发当前 Widget 及其子树的 build () 方法重新执行。
当调用 setState 方法时,Flutter 会标记当前的 Widget 为”dirty”,表示该 Widget 需要重新绘制。然后,Flutter 会在下一帧(即下一个绘制周期)中执行重绘操作。
在重绘过程中,Flutter 会使用 diff 算法来比较前后两次状态的差异,并只重新绘制发生变化的部分。这意味着,只有受到 setState 调用影响的部分会被重新绘制,而不是整个界面。
为什么小的状态变化导致较大范围的重建
1. Flutter 的渲染机制
Flutter 是 声明式 UI:
- 你在
build()方法里声明 UI 的“长相”。 - 当
setState触发时,框架会丢弃旧的 widget tree(当前组件和子树),重新执行build()得到一棵新的 widget tree。 - 然后 Flutter 的 Element tree 和 RenderObject tree 会做 diff,决定哪些节点需要真正重新渲染。
也就是说,rebuild 的范围是 Widget 层(当前组件及子树),但最终渲染层(RenderObject)可能会更小,因为 Flutter 做了优化。
🔹 2. 什么时候会“大范围重建”
问题出在:setState 总是作用在当前 widget 及子树,所以如果 widget 设计不合理,哪怕只是一个小状态变化,也会导致整块 UI 重建。
举例:
1 | |
这里的问题
- 我只是改了
counter,但是BigPage整个build()重新执行。 - 那个
ListView.builder也被 rebuild(虽然 Flutter 会尽量复用 child,但itemBuilder还是会再次调用)。 - 如果 UI 很大,逻辑很重,就会性能浪费。
🔹 3. 如何避免大范围重建
核心思想:拆分 widget,让状态尽量只影响局部。
改造上面例子:
1 | |
结果:
- 点按钮时,
BigPage和CounterText会 rebuild。 - 但
HeavyList不会重新 build,性能大幅优化。因为是 StatelessWidget,并且拆分出去了
🔹 4. 总结
👉 小状态变化导致大范围重建的情况:
- 状态放在大组件里(页面级 State)。
setState导致整个大组件及其子树 rebuild。- 子树里有很复杂的 UI,哪怕它没用到这个状态,也要被 rebuild。
👉 解决方法:
- 拆分组件(局部 widget),减少无关 UI rebuild。
- 或者用 状态管理框架(Provider、Riverpod、Bloc 等)精准刷新。
fluuter_boost
fluuter_boost 跳转页面比较方法。api 统一(原生 flutter),有生命周期,好管理
fluuter_boost3.0 不兼容最新的flutter版本,需要修改flutter_boost源码,暂时放弃该方案
FlutterBoost 是阿里开源的 Flutter 混合开发解决方案,主要解决 原生应用(Android/iOS)与 Flutter 模块的集成问题。
FlutterBoost 的好处就是让 Flutter 像“原生页面”一样融入现有 App,解决路由、生命周期、引擎复用等问题,让混合开发变得平滑、高效。
路由与原生一致
- FlutterBoost 提供 统一的路由栈,支持原生跳 Flutter,Flutter 跳原生,Flutter 跳 Flutter。
- 开发者不用自己维护复杂的
Navigator和原生交互。
👉 保证 Flutter 页面和原生页面一样被管理,不会出现返回栈混乱。
状态管理框架
Flutter 状态管理框架 Provider 和 Get 分析
解决逻辑和页面 UI 耦合问题
我们知道 Dart 是一种单线程的模型,所以不存在多线程下对于对象访问的竞态问题。基于此 Get 借助一个全局单例的 Map 存储对象。通过依赖注入的方式,实现了对 Presenter 层的获取。这样在任意的类中都可以获取到 Presenter。
解决难以跨组件 (跨页面) 访问数据的问题
- 全局单例,任意位置可以存取
- 存在类型重复,内存回收问题
setState 引起不必要刷新的问题
在 Get 中,只需要提前调用 Get.put 方法存储 Counter 对象,为 GetBuilder 组件指定 Counter 作为泛型。因为 Get 基于单例,所以 GetBuilder 可以直接通过泛型获取到存入的对象,并在 builder 方法中暴露。这样 Counter 便与组件建立了监听关系,之后 Counter 的变动,只会驱动以它作为泛型的 GetBuilder 组件更新。
下面详细解释
1 | |
Get 由于全局单例带来的问题
Get 通过全局单例,默认以 runtimeType 为 key 进行对象的存储,部分场景可能获取到的对象不符合预期,例如商品详情页之间跳转。由于不同的详情页实例对应的是同一 Class,即 runtimeType 相同。如果不添加 tag 参数,在某个页面调用 Get.find 会获取到其它页面已经存储过的对象。同时 Get 中一定要注意考虑到对象的回收,不然很有可能引起内存泄漏。要么手动在页面 dispose 的时候做 delete 操作,要么完全使用 Get 中提供的组件,例如 GetBuilder,它会在 dispose 中释放。
GetX使用
GetxController
1 | |
🔹 代码整体逻辑
这是用 GetX 的 GetBuilder 做状态管理的典型写法。
核心思想
Counter是一个继承GetxController的状态类。- 你把
Counter实例存到 GetX 的全局容器里(类似单例)。 GetBuilder<Counter>会去取这个Counter实例,并建立监听。- 当
Counter.update()被调用时,只有用到这个Counter的GetBuilder才会刷新。
🔹 逐行解释
1. 定义控制器
1 | |
Counter继承自GetxController,说明它是一个 可管理的状态类。count是状态数据。increase()修改count,然后调用update()。
👉update()会通知所有依赖这个控制器的GetBuilder组件去刷新。
2. 存储控制器
final counter = Get.put(Counter());
Get.put()是 依赖注入(DI)。- 这句话等价于:把一个
Counter实例存到 GetX 的全局容器里,方便后续随时取用。 - 由于 GetX 默认是 单例模式,所以无论在哪个地方用
Get.find<Counter>(),得到的都是同一个Counter对象。
3. 使用 GetBuilder 绑定 UI
GetBuilder<Counter>( builder: (Counter counter) => Text('${counter.count}') );
GetBuilder<Counter>的泛型<Counter>告诉 GetX:
👉 我要监听Counter这个控制器。builder会传入存好的Counter实例(就是Get.put()存的那个)。- 当
counter.update()被调用时,只有这个GetBuilder<Counter>会刷新。 - UI 更新:
Text('${counter.count}')会显示最新的值。
🔹 和 setState 的区别
setState会重建 当前 Widget 及子树。GetBuilder只会更新绑定了Counter的部分,精确更新,不影响其他 UI。- 而且状态
Counter是全局单例,多个页面/组件都能共享。
🔹 总结一句话
Get.put(Counter())把状态对象存起来,GetBuilder<Counter>绑定到这个状态,
当Counter.update()被调用时,只有依赖它的GetBuilder会刷新。