Flutter 刷新方案对比

4,380 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

一. 前言

目前Flutter各种状态管理框架层出不穷,这篇文章将带大家使用StatefulWidgetProviderGetX各自实现一个计数器。

tip:对比这三种方案,结合实现后的代码量与性能,让大家可以根据实际需求进行更优选择。

二. 实现

实现目标:以一个计数器为例,点击增加按钮时,刷新屏幕中心Text Widegt。

  • StatefulWidget
  1. 当我们使用StatefulWidget实现时,首先定义一个变量:
 int _counter = 0;
  1. 点击增加按钮,调用:
  void _incrementCounter() {

    setState(() {

      _counter++;

    });

  }

_incrementCounter方法通过调用setState,我们就实现了页面刷新。代码非常简单,但通过Android Studio上的Performance工具,我们可以看到,当我们点击按钮时,不仅仅展示计数的Text被刷新了,我们整个页面的其他组件(除了被const关键字声明的)都随着点击按钮的增加都进行了相应次数的重绘。这在项目中是无法接受的,我们只想刷新一个文本,却将整个页面进行了重绘。

  • Provider
  1. 当我们使用Provider实现时,首先定义一个CounterProvider类
class CounterProvider extends ChangeNotifier {

  var count = 0;

  void increment() {
    count++;
    notifyListeners();//通知更新
  }

}
  1. 将我们页面采用ChangeNotifierProvider包裹
@override

Widget build(BuildContext context) {
  return ChangeNotifierProvider(
    create: (BuildContext context) => CounterProvider(),
    builder: (context, child) => _buildPage(context),
  );
}
  1. 将我们的文本使用Consumer包裹
Consumer<CounterProvider>(
  builder: (context, provider, child) {
    return Text('点击了 ${provider.count} 次');
  },
)
  1. 点击增加按钮,调用Provider increment方法:
floatingActionButton: FloatingActionButton(
  onPressed: () => context.read<CounterProvider>().increment(),//按钮事件
  child: const Icon(Icons.add),
)

至此,一个使用Provider实现的计数器就实现了。通过Performance工具分析,点击计数按钮时,只有展示计数的Text与自身Consumer被刷新了。页面上其他组件都没有随着点击次数的增多而进行额外刷新。这种刷新方式显然是比StatefulWidget要优雅很多,但代价是需要增加很多代码。

  • GetX
  1. 当我们使用GetX时,首先定义一个变量,为了使其可观察,我们在变量后增加.obs
 var _counter = 0.obs;
  1. 点击增加按钮,调用:
void _incrementCounter() => _counter++;
  1. 将我们的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方法)。在表单这类需要控制多个组件但同时只需单一组件刷新的场景下,将会减少很多的工作量并提升刷新性能。