3f83e81c9578f74f1cfe2e89247359db
Flutter 状态管理 0x00 - 基础知识及 State.setState 背后逻辑

前言

因为之前我的文章大多是源码剖析相关的,所以这次决定换种方式先从 Flutter 开发的状态管理聊起。

本文会简单介绍 Flutter 以及声明式编程思想和代码画风,对比 StatelessWidget & StatefulWidget 这两个重要的 Widget,再聊聊 setState 背后的那些事儿。

索引

  • Flutter 简介
  • 声明式 UI & 响应式编程
  • Flutter 如何渲染 Widget
  • StatelessWidget & StatefulWidget
  • State.setState 背后的那些事儿

Flutter 简介

Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台。

Write once, run anywhere.」始终是大前端开发乃至整个程序界的一个永恒的话题。诚然 Flutter 的目标也是如此,不过它与之前业界已经存在的 React Native、Xamarin、Hybrid 等框架有何不同?

从上图可以看到 Flutter 的设计整体上有三层抽象:

  • 最上层 Framework 封装好了 Material 和 Cupertino 这两种分别对应 Android 和 iOS 官方设计风格的 UI 库,除此之外还包含了渲染、动画、绘制、手势等等,这一层主要是提供给开发者便捷构建 App 使用的,其在开发时为 JIT、发布时为 AOT 的特性兼顾了开发调试的便捷与线上运行的高性能;
  • Engine 作为中间层,使用 C/C++ 实现了一套高性能、可移植的 Runtime,屏蔽了平台间差异的同时支撑了上层的 Flutter 应用。
  • Embedder 由平台指定语言实现,提供了 Flutter 所需的事件循环、线程、渲染等基础 API。

Flutter 通过这套机制接管了最底层的系统接口,提供了一整套从底层渲染逻辑到上层开发语言的完整解决方案,使得它有着超越 React Native 的高保真、多端一致的体验,和超越 Web 容器的高性能渲染能力。

声明式 UI & 响应式编程

声明式 UI

这张图很好的描述了声明式 UI 的核心思想,简单来说就是通过 state 作为入参根据已经写好的构建 func 就能得到我们想要的 UI 效果。举个 🌰 更直观:

默认的 Flutter Demo 界面:

对应的代码:

return Scaffold(
  appBar: AppBar(
    title: Text(widget.title),
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(
          'You have pushed the button this many times:',
        ),
        Text(
          '$_counter',
          style: Theme.of(context).textTheme.display1,
        ),
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    // _incrementCounter 内部逻辑可忽略
    onPressed: _incrementCounter,
    tooltip: 'Increment',
    child: Icon(Icons.add),
  ),
);

In flutter, everything is a widget.

Emmmmm...可以注意到上面的代码中 children: <Widget>[...] 一行,Flutter 开发中就是通过这种方式嵌套组合 Widget 来描述用户界面的。从这一角度讲,Widget 是对一部分用户界面的不可变描述。

默认的 Flutter Demo 跑起来很简单,推荐大家亲身体验。简单说一下我的感受,声明式 UI 的优势很大,具体表现为只需要在写 UI 时将不同 state 对应的 UI 展示考虑并描述清楚就可以省去后续 state 变更时命令式 UI 需要手动管理视图层级(新增或删除视图元素)和更新属性(颜色、字号等)等麻烦。

值得一提的是 SwiftUI 也使用了声明式 UI 的思想(声明式 UI 才是未来)。

响应式编程

响应式编程 ,在计算机领域,响应式编程是一个专注于数据流和变化传递的异步编程范式。
这意味着可以使用编程语言很容易地表示静态(例如数组)或动态(例如事件发射器)数据流,
并且在关联的执行模型中,存在着可推断的依赖关系,这个关系的存在有利于自动传播与数据流有关的更改。

响应式编程对于大家来说应该早已不是什么新鲜事物了,单在移动客户端领域就有诸如 ReactiveCocoaRxSwiftRxJava、RxXxx...简单来说这种编程思想或者说范式下开发者只需关注可能存在的数据状态以及与之对应的逻辑从而大大减轻了维护这些对应关系与状态细节的工作负担。因为用过的人都说好,所以目前渐渐被推为移动客户端乃至整个大前端的主流编程思想。

不难看出其实声明式 UI 和响应式编程从思想上是完全契合的,我们只需要将数据流对应到 UI=f(state) 公式中的 state 就可以了。

Flutter 如何渲染 Widget

在介绍 StatelessWidgetStatefulWidget 之前先要了解一下 Flutter 的渲染模型,简单来讲 Flutter 在渲染 Widget 时用到三棵树:

  • Widget,负责描述 Element 的配置。
  • Element,负责管理 Widget 和 RenderObject 的生命周期。
  • RenderObject,负责控制尺寸,布局和绘制工作。

一言以蔽之,Element 会根据我们书写的 Widget 对其的配置描述来生成并管理对应的 Element 与 RenderObject,由 RenderObject 负责最终的绘制工作。

NOTE: Element 会根据 Widget 的描述最大程度复用现有 Element 与 RenderObject 以提升性能。

StatelessWidget & StatefulWidget

StatelessWidget

StatelessWidget, A widget that does not require mutable state.

StatelessWidget 如其名 Stateless,它不需要追踪 State 并根据 State 更新 UI,所以一般 StatelessWidget 内部的属性用 final 修饰声明且构造方法一般以 const 修饰。

NOTE: const 修饰的 Widget 构造方法使用时可以提效。

StatefulWidget

StatefulWidget, A widget that has mutable state.

StatefulWidgetStatelessWidget 一样,也可以通过一系列(组合嵌套)其他更具体描述 UI 的 Widget(比如 Text)来构建部分用户界面。区别在于 Stateful,即它需要追踪 State 并根据 State 更新 UI。

区别

StatelessWidgetStatefulWidget 大概是我们用 Flutter 技术栈开发 App 时最常打交道的两个 Widget 了。

共同点:

  • 这两个 Widget 都可以用来通过对其他一系列 Widget 构建完成一部分用户界面的封装。

不同点:

  • StatelessWidget 更适用于一些不需通过用户交互或其他原因通过 State 控制更新的 UI 封装。
  • StatefulWidget 更适用于追踪某个会根据用户交互或其他因素影响的 State,并根据最新的 State 实时更新的 UI 封装。

此外,Flutter 对于 StateLessWidgetStatefulWidget 的绘制也有差异:

  • StatelessWidget 通过 StatelessElement.build 触发 build
  • StatefulWidget 通过 StatefulElement.build 触发 State.build

State.setState 背后的那些事儿

NOTE: 下面分析的 State.setState 源码版本为 Flutter SDK v1.9.1+hotfix.6。

我们还以 Flutter 默认 Demo 来分析,在 Demo 中我们点击界面右下角的 FloatingActionButton (就是蓝色带有 + 号的圆形按钮)之后会刷新界面,屏幕中间的 Text Widget 会显示按钮被按下的次数。

FloatingActionButton 点击之后调用了 _incrementCounter 方法:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  ...
}

_MyHomePageState._incrementCounter 内部逻辑也仅仅是调用了 State.setState 而已。正如 Flutter 所宣传的公式 UI = f(state) 一样,开发者只需要调用 State.setState 传入 VoidCallback 内部写好相关数据更新逻辑即可更新 UI,那么 Flutter 是如何做到这一点的呢?

State.setState 背后逻辑

State.setState 背后调用嵌套较多,实际所做的事情理解起来却很简单。如不喜在文内阅读源码可直接跳转至「总结 State.setState 背后逻辑」小节看相关逻辑总结。

State.setState

``` dart
@optionalTypeArgs
abstract class State extends Diagnosticable {
...
@protected

top Created with Sketch.