【Flutter从入门到入坑之五】你真的会使用 Widget 中的 State 吗?

news2024/11/18 19:59:12

【Flutter从入门到入坑】Flutter 知识体系
【Flutter从入门到入坑之一】Flutter 介绍及安装使用
【Flutter从入门到入坑之二】Dart语言基础概述
【Flutter从入门到入坑之三】Flutter 是如何工作的
【Flutter从入门到入坑之四】构建Flutter界面的基石——Widget


State

    • 前沿
    • UI 编程范式
      • 命令式 VS 声明式
    • 通过源码看区别
      • StatelessWidget(无状态组件)
        • StatelessWidget 示意图
        • 源码分析
        • 什么场景下应该使用 StatelessWidget 呢?
        • 两个小例子辅助你理解一下
      • StatefulWidget(有状态组件)
        • StatefulWidget 示意图
        • 源码分析
    • StatefulWidget 不是万能的,要慎用
    • Widget 的更新机制;
    • 小结


前沿

我们上一节 【Flutter从入门到入坑之四】构建Flutter界面的基石——Widget 中,认识了 Widget、Element、RenderObject 是如何互相配合,实现图形渲染工作的。Flutter 在底层做了大量的渲染优化工作,使得我们只需要通过组合、嵌套不同类型的 Widget,就可以构建出任意功能、任意复杂度的界面。

我们了解到 Widget 有 StatelessWidgetStatefulWidget 两种类型。
StatefulWidget 应对有交互、需要动态变化视觉效果的场景;而 StatelessWidget 则用于处理静态的、无状态的视图展示。

StatefulWidget 的场景已经完全覆盖了 StatelessWidget,因此我们在构建界面时,往往会大量使用 StatefulWidget 来处理静态的视图展示需求,看起来似乎也没什么问题。

今天,我们将着重介绍这两种类型的区别,从而帮你更好地理解 Widget,掌握不同类型 Widget 的正确使用时机。

通过本篇文章,你可以学习到:

  • 通过源码,了解 StatelessWidget 与 StatefulWidget 的基本设计思路;
  • 重新了解 Widget 的 UI 更新机制;

UI 编程范式

首先,我们需要了解:在 Flutter 中,如何调整一个控件(Widget)的展示样式,即通过 UI 编程范式。

如果你有相关原生开发经验的话,应该知道 视图开发是命令式 的,需要精确地告诉操作系统或浏览器用何种方式去做事情。
一个很简单的例子:如果我们想要变更界面的某个文案,则需要找到具体的文本控件并调用它的控件方法命令,才能完成文字变更。

// 原生JavaScript设置某文本控件展示文案为Hello World
document.querySelector("#demo").innerHTML = "Hello World!";

与此不同的是,Flutter 的视图开发是声明式的,其核心设计思想就是将视图和数据分离,这与 React 的设计思路是一致的。

命令式 VS 声明式

命令式编程强调精确控制过程细节;而声明式编程强调通过意图输出结果整体。 对应到 Flutter 中,意图 是绑定了组件状态的 State,结果 则是重新渲染后的组件。在 Widget 的生命周期内,应用到 State 中的任何更改都将强制 Widget 重新构建。

对于组件完成创建后就无需变更的场景,状态的绑定是可选项。这里“可选”就区分出了 Widget 的两种类型,即:StatelessWidget 不带绑定状态,而 StatefulWidget 带绑定状态。当你所要构建的用户界面不随任何状态信息的变化而变化时,需要选择使用 StatelessWidget,反之则选用 StatefulWidget。 前者一般用于静态内容的展示,而后者则用于存在交互反馈的内容呈现中。


通过源码看区别

下面,详细介绍 StatelessWidgetStatefulWidget,从源码的角度去分析它们的区别,并总结一些关于 Widget 选型的基本原则。

StatelessWidget(无状态组件)

在 Flutter 中,Widget 采用由父到子、自顶向下的方式进行构建,父 Widget 控制着子 Widget 的显示样式,其样式配置由父 Widget 在构建时提供。

通过这种方式构建出的 Widget,比如 TextContainerRowColumn 等,在创建时,除了这些配置参数之外不依赖于任何其他信息,换句话说,它们一旦创建成功就不再关心、也不响应任何数据变化进行重绘。在 Flutter 中,这样的 Widget 被称为 StatelessWidget(无状态组件)。

StatefulWidget(有状态组件)

与 StatelessWidget 相对应的,有一些 Widget(比如 ImageCheckbox)的展示,除了父 Widget 初始化时传入的静态配置之外,还需要处理用户的交互(比如,用户点击按钮)或其内部数据的变化(比如,网络数据回包),并体现在 UI 上。
这些 Widget 创建完成后,还需要关心和响应数据变化来进行重绘。在 Flutter 中,这一类 Widget 被称为 StatefulWidget(有状态组件)。

  • StatefulWidget 示意图

    StatefulWidget 示意图
    如果你看过上一篇 【Flutter从入门到入坑之四】构建Flutter界面的基石——Widget,里面提到过:Widget 是不可变的,发生变化时需要销毁重建,所以谈不上状态。 你是否有所困惑呢?那么,这到底是怎么回事呢?

    其实,StatefulWidget 是以 State 类代理 Widget 构建的设计方式实现的。

    请往下看,我们以源码为例,来帮助你理解这个话题。

  • 源码分析

    下面,我们通过 Image 组件的部分源码,来说明 StatefulWidget 的构建过程,并且来辅助理解上面的这个知识点。

    和上面提到的 Text 一样,Image 类的构造函数会接收要被这个类使用的属性参数。然而,不同的是,Image 类并没有 build 方法来创建视图,而是通过 createState 方法创建了一个类型为 _ImageStatestate 对象,然后由这个对象负责视图的构建。

    这个 state 对象持有并处理了 Image 类中的状态变化,所以我就以 _imageInfo 属性为例来一起分析一下。

    _imageInfo 属性用来给 Widget 加载真实的图片,一旦 State 对象通过 _handleImageChanged 方法监听到 _imageInfo 属性发生了变化,就会立即调用 _ImageState 类的 setState 方法通知 Flutter 框架:“我这儿的数据变啦,请使用更新后的 _imageInfo 数据重新加载图片!”。而,Flutter 框架则会标记视图状态,更新 UI。

    请看源码

    class Image extends StatefulWidget {
      //构造方法及属性声明部分
      const Image({
        Key key,
         this.image,
        //其他参数
      }) : assert(image != null),
           super(key: key);
      final ImageProvider image;
      //其他属性
      ...
      
      
      _ImageState createState() => _ImageState();
      ...
    }
    class _ImageState extends State<Image> {
      ImageInfo _imageInfo;
      //其他属性
      ...
      void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
        setState(() {
          _imageInfo = imageInfo;
        });
      }
      ...
      
      Widget build(BuildContext context) {
        final RawImage image = RawImage(
          image: _imageInfo?.image,
          //其他初始化配置
          ...
        );
        return image;
      }
     ...
    }
    

    在这个例子中,Image 以一种动态的方式运行:监听变化,更新视图。与 StatelessWidget 通过父 Widget 完全控制 UI 展示不同,StatefulWidget 的父 Widget 仅定义了它的初始化状态,而其自身视图运行的状态则需要自己处理,并根据处理情况即时更新 UI 展示。


StatefulWidget 不是万能的,要慎用

上面我们已经通过 StatelessWidgetStatefulWidget 的源码,理解了这两种类型的 Widget。你可能会问,既然 StatefulWidget 不仅可以响应状态变化,又能展示静态 UI,那么 StatelessWidget 这种只能展示静态 UI 的 Widget,还有存在的必要吗?

答案是肯定的,当然有存在的必要!

对于 UI 框架而言,同样的展示效果一般可以通过多种控件实现。从定义来看,StatefulWidget 仿佛是万能的,替代 StatelessWidget 看起来合情合理。于是 StatefulWidget 也容易遭到滥用。

但事实是,StatefulWidget 的滥用会直接影响 Flutter 应用的渲染性能。

下面我们回顾一下 Widget 的更新机制,来帮你意识到完全使用 StatefulWidget 的代价。


Widget 的更新机制;

Widget 是不可变的,更新则意味着销毁 + 重建(build)。StatelessWidget 是静态的,一旦创建则无需更新;而对于 StatefulWidget 来说,在 State 类中调用 setState 方法更新数据,会触发视图的销毁和重建,也将间接地触发其每个子 Widget 的销毁和重建。

这也就意味着,如果我们的根布局是一个 StatefulWidget,在其 State 中每调用一次更新 UI,都将是一整个页面所有 Widget 的销毁和重建。

虽然 Flutter 内部通过 Element 层可以最大程度地降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个 RenderObject 树重建。但是大量 Widget 对象的销毁重建是无法避免的。如果某个子 Widget 的重建涉及到一些耗时操作,那页面的渲染性能将会急剧下降。

因此,正确评估你的视图展示需求,避免无谓的 StatefulWidget 使用,是提高 Flutter 应用渲染性能最简单也是最直接的手段。


小结

本章主要是基于 StatelessWidget 与 StatefulWidget 而展开的一些讨论。

  • 通过了解 Flutter 基于声明式的 UI 编程范式,并通过阅读两个典型 Widget(TextImage)源码的方式,学习了 StatelessWidget 与 StatefulWidget 的基本设计思路。

    由于 Widget 采用由父到子、自顶向下的方式进行构建,因此在自定义组件时,我们可以根据父 Widget 是否能通过初始化参数完全控制其 UI 展示效果的基本原则,来判断究竟是继承 StatelessWidget 还是 StatefulWidget。

  • 了解 StatefulWidget 不是万能的,重新回顾了 Widget 的 UI 更新机制。尽管 Flutter 会通过 Element 层去最大程度降低对真实渲染视图的修改,但大量的 Widget 销毁重建无法避免,因此避免 StatefulWidget 的滥用,是最简单、直接地提升应用渲染性能的手段。

  • 需要注意的是,除了我们主动地通过 State 刷新 UI 之外,在一些特殊场景下,Widget 的 build 方法有可能会执行多次。因此,我们不应该在这个方法内部,放置太多有耗时的操作。【关于此部分详细的讲解,我会放到下一章:生命周期中进行详细分析】


欢迎各位小伙伴在评论区留言分享你的观点,也欢迎各位大佬的一键三连,我会持续更新【Flutter从入门到入坑系列】,也欢迎你把这篇文章分享给更多的朋友一起阅读。

本章节内容主要是学习 陈航 老师的《Flutter 核心技术与实战》课程总结而来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/492479.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023 年 3 月青少年机器人技术等级考试理论综合试卷(一级)

2023 年 3 月青少年机器人技术等级考试理论综合试卷&#xff08;一级&#xff09; 一、单选题(共 30 题&#xff0c;共 60 分) 1.关于使用动滑轮说法正确的是&#xff1f;&#xff08; B&#xff09; A. 是否省力与动滑轮的颜色有关 B.可以改变力的大小 C.不能省力 D.动滑轮的位…

AUTOSAR知识点Com(十三):ComM内容分析

目录 1、概述 1.1、总览 1.2、功能描述 1.3、依赖关系 2、功能SPEC 2.1、PNC 2.2、通道状态机 2.3、时序图解析 3、COMM工具配置 3.1、ComMGeneral 3.2、ComMConfigSet 1、概述 1.1、总览 ComM的全程是Communication Manager 管理通信&#xff0c;是BSW里面的一个组…

关于大模型对未来影响的一点看法

人们总是高估了未来一到两年的变化&#xff0c;低估了未来十年的变革。 ---比尔盖茨 近来OpenAI的GPT技术可以说在全球都带来了巨大的影响&#xff0c;也让大家看到了什么叫大力出奇迹。chatGPT和GPT4的能力给了大家很大的震撼&#xff0c;其流畅自如、逻辑清晰、出众的能力&am…

【vscode远程开发】使用SSH远程连接服务器 「内网穿透」

文章目录 前言视频教程1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 转…

Rosetta从头蛋白抗体设计、结构优化及在药物研发中的应用

Rosetta从头蛋白抗体设计、结构优化及在药物研发中的应用 第一天 内容 主要知识点 从蛋白质折叠到蛋白质设计教学目标&#xff1a;了解本方向内容、理论基础、研究意义。 蛋白质折叠与结构预测简介 主链二面角与二级结构侧链堆积与三级结构蛋白质设计简介 蛋白质设计的分…

shell编程之条件语句__例题

文章目录 (1) 检查用户家目录中的test.sh文件是否存在&#xff0c;并且检查是否有执行权限. [rootclr /opt/mywork]# cat 1.sh #!/bin/bash #检查用户家目录中的 test.sh 文件是否存在&#xff0c;并且检查是否有执行权 cd /roottest -f test.shif [ $? -eq 0 ];thentest -x…

插件、组件、控件,你分得清吗?

最近和小伙伴交流&#xff0c;时常发生插件、组件、控件等概念混淆的情况&#xff0c;因此导致经常会错意。感觉还是很有必要带大家整理清楚的&#xff0c;今天就来跟大家来聊一聊插件、组件、控件的区别。 什么是插件 先按照官方的一些解释来看看插件的概念描述&#xff1f;…

搭建Debug(printf)Viewer调试环境

希望对KEIL开发的小白的有用&#xff0c;KEIL开发如果高效正确调用Printf 打印输出调试信息。 需要添加红色字体代码 #include <stdio.h> #define ITM_Port8(n) (*((volatile unsigned char *)(0xE00000004*n))) #define ITM_Port16(n) (*((volatile unsigned short *)(…

Android类似微信聊天页面教程(Kotlin)五——选择发送图片

前提条件 安装并配置好Android Studio Android Studio Electric Eel | 2022.1.1 Patch 2 Build #AI-221.6008.13.2211.9619390, built on February 17, 2023 Runtime version: 11.0.150-b2043.56-9505619 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. Windows 11 …

【模拟IC学习笔记】 负反馈系统的稳定性

反馈方程 输出方程 误差方程 环路增益 定义:当输入电压接地、并将环路断开时&#xff0c;所计算的增益就是环路增益 时&#xff08;幅度1&#xff0c;相位-180&#xff09;电路稳定在电源或低状态或在电源和地之间振荡。 分析稳定性时是对环路增益进行分析&#xff0c;通过环…

Java+Selenium+Testng自动化学习(一):环境搭建

目录 一、软件准备及版本介绍&#xff1a; 二、安装步骤及环境变量配置&#xff1a; 2.1 Java安装及配置 2.2 IDE工具&#xff08;Intellij IDEA&#xff09;安装及配置 2.3 Maven安装及配置 2.4 Maven本地仓库配置 2.5 Intellij IDEA配置mvn 一、软件…

全开放式时代来临,南卡OE带给你非同寻常的舒适和听觉感受

NANK南卡一直致力于深耕音频领域&#xff0c;不断创新&#xff0c;将于五月份发布全新形态的全开放式蓝牙耳机。相较于传统耳机&#xff0c;开放式蓝牙耳机除了佩戴零感舒适&#xff0c;还能在沉浸音乐的同时与外界保持沟通。南卡OE作为新推出的开放式蓝牙耳机&#xff0c;采用…

7年从测试废物到测试主管,如何从纯功能测试内卷中破局?

我大学学的计算机专业&#xff0c;有一点编程基础。我的软件测试职业开始和大多数测试人员一样&#xff0c;一开始在一家电商公司做软件功能测试。 大部分进入测试行业的朋友&#xff0c;最开始接触都是纯功能界面测试&#xff0c;随着工作年限&#xff0c;会接触到一些常用测试…

计算机体系结构总结:内存一致性模型 Memory consistency Model

存储一致性是为了保证多线程背景下的访存顺序&#xff0c;多线程的语句是可以交错执行&#xff0c;使得顺序不同产生不同的执行结果。 下面P2的输出结果可能是什么&#xff1f; P1, P2两个线程的语句是可以交叉执行的&#xff0c;比如1a, 2a, 2b, 1b&#xff1b;一个线程内的语…

网络基础学习:什么是网络与网络发展史

什么是网络与网络发展史 什么是网络&#xff1f;什么是网络发展史&#xff1f;分组交换技术TCP/IP技术Web技术ARPANET&#xff08;1969年&#xff09;Internet&#xff08;1983年&#xff09;万维网&#xff08;1990年&#xff09;移动互联网&#xff08;2007年&#xff09;物联…

Golang - GraphQL 搭配 Database

介绍 上一篇我们用GraphQL 已经有个基本的认识 接着来模拟真实的情境搭配Database 来使用 这篇主要都是在初始化环境 示例代码 通过 Docker 设置 MySQL 数据库 从 DockerHub 拉取 MySQL 镜像docker pull mysql 创建MySQL数据库 $ docker exec -it mysql bash $ mysql -u …

Python代码学习之给图片添加文字或图片水印

前言 图片加水印有什么好处&#xff1f;在现今的数字化时代&#xff0c;网络上的图片泛滥&#xff0c;盗图现象也越来越严重。因此&#xff0c;在发布文章时&#xff0c;为了保护自己的原创作品版权&#xff0c;很多人选择使用水印来保护他们的图片。这样就能更好地做到&#…

Java基础(十九)反射机制

1. 反射(Reflection)的概念 1.1 反射的出现背景 Java程序中&#xff0c;所有的对象都有两种类型&#xff1a;编译时类型和运行时类型&#xff0c;而很多时候对象的编译时类型和运行时类型不一致。 Object obj new String(“hello”); obj.getClass() 例如&#xff1a;某些变…

ChatGPT技术如何助力汽车门店销售服务水平提升?

过往&#xff0c;由于线下销售过程没有数字化记录&#xff0c;销售顾问的销讲要点执行情况、客户在体验展车、试乘试驾等过程中的反馈&#xff0c;没法真实全面地记录下来&#xff0c;因此很难做精细化的销售管理和客户心声分析。销售沟通过程不透明、员工服务质量难评估。 在…

Docker启动多个mysql容器

原有镜像mysql5.7.41&#xff0c;已启动mysql容器端口3306&#xff0c;再启动一个端口号为3400的容器。指定用户名root,mima &#xff0c;密码123456 命令&#xff1a; docker run --name zjfz-mysql -e MYSQL_ROOT_PASSWORD123456 -p 3400:3306 -d mysql 名字&#xff1a;zjfz…