Flutter 入门与实战(四十三):setState 和 ModelBinding用法对比

1,830 阅读4分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

前言

上两篇我们介绍了使用 InheritedWidget 深入状态管理,并且解耦了组件和状态管理,从而使得代码更易于维护,而且还能实现局部刷新的效果。上两篇文章传送门:

到底能不能实现局部刷新呢吗,我们本篇通过一个示例来验证一下。

假设

回到我们前一个故事 —— 小芙和雷思相亲的故事,假设我们的雷思不是那么木,他能够读懂小芙的心思(共享状态),那么也许结局可能就不是之前那样。所谓心有灵犀一点通,我们分别通过ModelBinding和初级的setState方式来实现这样的效果。

使用 ModelBinding 实现状态共享

为了使用 ModelBinding 实现状态共享,我们需要将雷思和小芙作为ModelBinding的子组件。同时,我们另外写了一个不依赖于状态的组件(StatelessNoDepend),也放直在 ModelBinding 的子组件中,以便验证局部刷新是否有效。

class StatefulStatelessDemoPage extends StatelessWidget {
  StatefulStatelessDemoPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('3年后'),
      ),
      body: ModelBindingV2(
        child: Column(
          children: [
            Xiaofu3(),
            Leisi(),
            StatelessNoDepend(),
          ],
        ),
        create: () => FaceEmotion(emotion: '惊讶'),
      ),
    );
  }
}

同时,在雷思(Leisi) 组件中,我们通过一个文本来显示状态中的数据,这里是一个情绪描述字符串。这个就是从状态管理中共享得到,因为依赖于状态,因此在状态改变的时候会重新build。

@override
Widget build(BuildContext context) {
    print('ModelBinding=====build:雷思');
    return Center(
      child: Text(
          'ModelBinding=====雷思感受到了小芙的${ModelBindingV2.of<FaceEmotion>(context).emotion}'),
    );
 }

同样的,在小芙(Xiaofu3)中,我们也会读取状态的情绪字符串,以及使用了一个按钮来改变情绪,从而使得组件刷新。

@override
Widget build(BuildContext context) {
    print('ModelBinding=====build:小芙');
    return Center(
      child: Column(children: [
        Text(
            'ModelBinding=====小芙的表情:${ModelBindingV2.of<FaceEmotion>(context).emotion}'),
        TextButton(
            onPressed: () {
              ModelBindingV2.update<FaceEmotion>(
                  context, FaceEmotion(emotion: '高兴'));
            },
            child: Text('小芙表情变了')),
      ]),
    );
}

为了看是否真的被重建,我们在三个组件(Leisi,Xiaofu3和StatelessNoDepend)中的build 方法里打印了一个信息,同时我们在构造函数也打印了构造信息。运行后,我们点击按钮,整个打印的信息如下:

flutter: ModelBinding=====constructor: 小芙

flutter: ModelBinding=====constructor: 雷思

flutter: constructor: 不依赖状态的组件

flutter: ModelBinding=====build:小芙

flutter: ModelBinding=====build:雷思

flutter: build:不依赖于状态的组件
-------------------------------

flutter: ModelBinding=====build:小芙

flutter: ModelBinding=====build:雷思

分隔线以下是点击按钮后的打印信息,可以看到,第一次加载的时候,三个组件都 build 方法都被调用了。而点击按钮后,只有 Leisi 和 Xiaofu 的build 方法被调用,这说明了确实实现了局部刷新的效果 —— 不依赖于状态的组件不会被重建。

使用 setState 共享状态

使用 setState的话会要复杂一点,我们需要通过父组件将数据传递给子组件,然后在状态发生改变的时候,需要调用 setState 方法更新子组件,这个时候还需要父组件的更新状态方法传递到更改状态的子组件里。显然,耦合度是很高的。

class SetStateDemo extends StatefulWidget {
  SetStateDemo({Key key}) : super(key: key);

  _SetStateDemoState createState() => _SetStateDemoState();
}

class _SetStateDemoState extends State<SetStateDemo> {
  FaceEmotion faceEmotion = FaceEmotion();

  void updateEmotion(FaceEmotion newEmotion) {
    if (faceEmotion != newEmotion) {
      setState(() {
        faceEmotion = newEmotion;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('setState 方式'),
      ),
      body: Column(
        children: [
          StatelessXiaofu(face: faceEmotion, updateEmotion: updateEmotion),
          StatelessLeisi(face: faceEmotion),
          StatelessNoDepend(),
        ],
      ),
    );
  }
}

剩下的代码很简单,就不贴了,现在看进入页面和点击按钮更改状态后的整个过程。

flutter: setState=====constructor:小芙

flutter: setState=====constructor: 雷思

flutter: constructor: 不依赖状态的组件

flutter: setState=====build:小芙

flutter: setState=====build:雷思

flutter: build:不依赖于状态的组件
------------------------------

flutter: setState=====constructor:小芙

flutter: setState=====constructor: 雷思

flutter: constructor: 不依赖状态的组件

flutter: setState=====build:小芙

flutter: setState=====build:雷思

flutter: build:不依赖于状态的组件

可以看到,第一次进入页面的过程和ModelBinding是一样的。但是,在点击按钮调用setState方法的时候就完全不一样了,使用ModelBinding方法只是调用了 依赖于状态的build方法,而setState之后全部子组件被重新构造了一遍,也就是移除后再插入了新的组件——这就性能消耗和 ModelBinding相比,肯定高很多。那么,setState 的过程到底发生了什么?我们下一篇通过源码来分析一下。

总结

本篇对比了使用 InheritedWidget 实现状态共享和使用 setState 方式实现状态共享的区别,很明显,使用 InheritedWidget的方式性能更高,可以实现局部刷新,而且不会出现 setState 那种重构整个组件树的情况。这个特点十分重要,意味着我们要尽可能地避免在高层级的组件上直接使用 setState刷新界面,而是要依赖于状态管理实现局部刷新。当然,如果这个组件本身是组件树的叶子节点,那么使用 setState 不会有什么性能损失,这个时候倒是没必要非得使用状态管理工具。


我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章。

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!