持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
一. 前言
目前Flutter各种状态管理框架层出不穷,这篇文章将带大家使用StatefulWidget、Provider与GetX各自实现一个计数器。
tip:对比这三种方案,结合实现后的代码量与性能,让大家可以根据实际需求进行更优选择。
二. 实现
实现目标:以一个计数器为例,点击增加按钮时,刷新屏幕中心Text Widegt。
- StatefulWidget
- 当我们使用StatefulWidget实现时,首先定义一个变量:
int _counter = 0;
复制代码
- 点击增加按钮,调用:
void _incrementCounter() {
setState(() {
_counter++;
});
}
复制代码
_incrementCounter方法通过调用setState,我们就实现了页面刷新。代码非常简单,但通过Android Studio上的Performance工具,我们可以看到,当我们点击按钮时,不仅仅展示计数的Text被刷新了,我们整个页面的其他组件(除了被const关键字声明的)都随着点击按钮的增加都进行了相应次数的重绘。这在项目中是无法接受的,我们只想刷新一个文本,却将整个页面进行了重绘。
- Provider
- 当我们使用Provider实现时,首先定义一个CounterProvider类
class CounterProvider extends ChangeNotifier {
var count = 0;
void increment() {
count++;
notifyListeners();//通知更新
}
}
复制代码
- 将我们页面采用ChangeNotifierProvider包裹
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => CounterProvider(),
builder: (context, child) => _buildPage(context),
);
}
复制代码
- 将我们的文本使用Consumer包裹
Consumer<CounterProvider>(
builder: (context, provider, child) {
return Text('点击了 ${provider.count} 次');
},
)
复制代码
- 点击增加按钮,调用Provider increment方法:
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterProvider>().increment(),//按钮事件
child: const Icon(Icons.add),
)
复制代码
至此,一个使用Provider实现的计数器就实现了。通过Performance工具分析,点击计数按钮时,只有展示计数的Text与自身Consumer被刷新了。页面上其他组件都没有随着点击次数的增多而进行额外刷新。这种刷新方式显然是比StatefulWidget要优雅很多,但代价是需要增加很多代码。
- GetX
- 当我们使用GetX时,首先定义一个变量,为了使其可观察,我们在变量后增加.obs
var _counter = 0.obs;
复制代码
- 点击增加按钮,调用:
void _incrementCounter() => _counter++;
复制代码
- 将我们的Text Widget用Obx包裹
Obx (() => Text ('$_counter'))
复制代码
上面的计数器在使用GetX时,仅仅需要添加三行代码就可以实现,并且通过Performance工具分析,我们发现当我们点击计数按钮时,只有展示计数的Text与自身Obx被刷新了。页面上其他组件都没有随着点击次数的增多而进行额外刷新。对比上方的两种实现,这种从代码量以及刷新范围上显得简单&非常优雅。
三. 结论
StatefulWidget虽然实现方便,但其本身没有分层概念,并且刷新范围太大,并不推荐在完整的页面调用其setState方法。如果实在要用,应该根据实际情况将Widget拆分,仅用来控制自身组件的状态,或者用来处理有动画的Widget。
Provider 虽然能细分刷新范围,且做了逻辑与UI分离。在页面复杂的情况下,如果一个Provider内部多个组件分别被Consumer包裹,当我们更新一个属性后,由于调用了notifyListeners方法,其他被Consumer包裹的组件也会被刷新。这时就需要我们针对Widget细分Provider。增加了使用难度。建议仅在页面需要整体刷新时使用。
GetX 在做到局部刷新的同时,也可以做到逻辑与UI分离。同时针对复杂的业务场景,一个obs属性关联一个或几个Obx控件,使用非常方便。内部通过观察者订阅的模式将obs与Obx进行绑定。(本质刷新是采用了StatefulWidget 的setState方法)。在表单这类需要控制多个组件但同时只需单一组件刷新的场景下,将会减少很多的工作量并提升刷新性能。