Flutter开发setState能否在build中直接调用详解
作者:deepfunc 发布时间:2022-05-17 14:55:27
两种情况
setState() 能在 build() 中直接调用吗?答案是能也不能。
来看一段简单的代码:
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
const TestPage({super.key});
@override
State<TestPage> createState() => _State();
}
class _State extends State<TestPage> {
int _count = 0;
@override
Widget build(BuildContext context) {
setState(() {
_count++;
});
return Scaffold(
appBar: AppBar(
title: const Text('测试页面'),
),
body: Center(
child: Text(
'$_count',
style: const TextStyle(fontSize: 24),
),
),
);
}
}
跑起来后代码不会报错,Text('$_count') 显示结果是 1,看来 build() 调用 setState() 没啥问题呀。小改一下,来看看这个:
class _State extends State<TestPage> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('测试页面'),
),
body: Center(
child: Builder(
builder: (context) {
setState(() {
_count++;
});
return Text(
'$_count',
style: const TextStyle(fontSize: 24),
);
}
),
),
);
}
}
改动主要是在 Text 上面加了一个 Builder,然后把 setState() 放在了 Builder 的 builder 中去调用。运行起来,结果出现报错了:The following assertion was thrown building Builder(dirty): setState() or markNeedsBuild() called during build.
提示在 Builder 的 build() 过程中出现了断言错误:build() 中不能调用 setState() 或 markNeedsBuild()。
这是什么情况呢,为什么第一种情况下可以在 build() 中调用 setState() 而第二种情况不行?下面来简单地分析下其中包含的原理。
原理分析
先说一下结论,在 build() 中直接调用 setState() 要满足一个前提条件:
如果当前有组件 A 处于 build() 中,那么 setState() 引起 rebuild 的组件必须是 A 或者 A 的子孙组件,不能是 A 的祖先组件。
这是因为组件 build 的顺序是从父到子,如果在子组件 build 的过程中执行 setState() 之类会引起父组件的重新 build 那就死循环肯定是不行的。
接下来看下 Flutter 源码中是如何判断和控制的。setState() 的内部会调用 _element!.markNeedsBuild()
,markNeedsBuild()
中有如下代码:
void markNeedsBuild() {
// ...
// 前半部分,断言重新 build 是否满足上面说的前提。
assert(() {
if (owner!._debugBuilding) {
assert(owner!._debugCurrentBuildTarget != null);
assert(owner!._debugStateLocked);
// _debugIsInScope() 用来判断是否满足前提条件。
if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) {
return true;
}
if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
final List<DiagnosticsNode> information = <DiagnosticsNode>[
ErrorSummary('setState() or markNeedsBuild() called during build.'),
// ...
];
// ...
}
// ...
}());
// ...
}
markNeedsBuild() 代码的前半部分有断言来处理是否满足上面说到的前提条件,_debugCurrentBuildTarget
就是当前正处于 build 状态的 element。_debugCurrentBuildTarget()
的内容如下:
bool _debugIsInScope(Element target) {
Element? current = this;
while (current != null) {
if (target == current) {
return true;
}
current = current._parent;
}
return false;
}
_debugIsInScope() 中的 this
就是调用 setState() 会引起 rebuild 的组件,target
就是当前正处于 build 的组件。其中的 while
循环会逐步比对 current 及其父组件是否当前 build 的对象,找到了才会返回 true,否则就是 false。如果是 false,则后面的断言就会出现错误:setState() or markNeedsBuild() called during build.
如果当前有组件正在 build 那么决不能引起父组件的 rebuild,我们来看下前面举例报错的第二种情况。Builder 是 TestPage 的子组件,Builder 的 builder 方法里调用的 setState 是 TestPage 上的,也就是在子组件的 build 过程中使父组件 rebuild 了,那么就会引起断言失败;而第一种情况下是在 TestPage 的 build 过程中调用 setState 使自己重新 rebuild,可以满足结论的前提,所以是可以调用的。
这里我们可以接着想下在第一种情况下,组件自己的 build 过程中调用了 setState 引起了自己重新 rebuild 的时候不是也会死循环了吗?我们接着看下 markNeedsBuild()
的后半部分代码,如果断言成功后后面的逻辑:
void markNeedsBuild() {
// ...
// 前半部分是断言。
if (dirty) {
return;
}
_dirty = true;
owner!.scheduleBuildFor(this);
}
这里可以看到组件在 build 过程中 markNeedsBuild()
会使组件变为 dirty 状态,这个时候在 build 中直接调用 setState 后发现已经是 dirty 状态后会直接返回,而不会调度重新 build,所以就没有问题了。
来源:https://juejin.cn/post/7153186376433795108


猜你喜欢
- Android 代码写控件代替XML简单实例简单的一个Button控件的练习。实现代码:Button btn = new Button(Ha
- 具体实现过程请看下面代码:简单的调用了一下系统的拍照功能代码如下所示://拍照的方法 private void openTakePhoto(
- 前言为什么要学SpringBoot和Mybatis呢,我觉得作为一个合格的后端程序员增删改查(CRUD)肯定是要会的,而且是最基本的一项技能
- 一、简介众所周知,值类型变量不能null,这也是为什么它们被称为值类型。但是,在实际的开发过程中,也需要值为null的一些场景。例如以下场景
- 题目描述Java创建线程的几种方式Java使用Thread类代表线程,所有线程对象都必须是Thread类或者其子类的实例。Java可以用以下
- 我们平时使用的一些常见队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了De
- 一、概述解决ANR一直是Android 开发者需要掌握的重要技巧,一般从三个方面着手。开发阶段:通过工具检查各个方法的耗时,卡顿情况,发现一
- SpringDataJpa like查询无效这里写自定义目录标题SpringDataJpa like查询@Query(value = &qu
- 呼吸按钮是我最早接触到为view添加动画效果的需求,刚刚参加安卓开发工作,要求设计一个好看的语音按钮效果,就有了这个成果,但是后来又改方案了
- 概述最近重新回顾了一下数据结构和算法的一些基本知识,对几种排序算法有了更多的理解,也趁此机会通过博客做一个总结。1.选择排序-简单选择排序选
- 在实际的工作中直接使用反射的机会比较少,有印象的就是一次自己做的WinForms小工具的时候利用反射来动态获取窗体上的每个控件,并且为必要的
- 一.认识IO1.IO的分类(1)BIO:同步阻塞IO(2)NIO:同步非阻塞IO(3)AIO:异步阻塞IO注意: 这里主要介绍BIO2.IO
- 本文实例为大家分享了Android仿京东分类效果展示的具体代码,供大家参考,具体内容如下1.写一个fragmentimport androi
- 题目描述BM99 顺时针旋转矩阵描述 有一个NxN整数矩阵,请编写一个算法,将矩阵顺时针旋转90度。 给定一个NxN的矩阵,和矩阵的阶数N,
- 项目中最近用到各种图表,本来打算用第三方的,例如MPAndroid,这是一个十分强大的图表库,应用起来十分方便,但是最终发现和设计不太一样,
- 笔者前段时间在做react-native开发,一直是有线连接安卓真机进行调试的。有线调试确实带来诸多麻烦,因为在调试过程中需要频繁和手机进行
- 前言本文将带您了解在 Flutter 中制作翻转卡片动画的两个完整示例。第一个示例从头开始实现,第二个示例使用第三方包。闲话少说,让我们动手
- 一.整型变量1.基本格式int 变量名 = 初始值;代码示例:public class CSDN {public static void m
- 一.抽象类(一)概念 在继承的层次结构中,每个新的子类都使类变得更加明确和具体。如果从一个子类向父类
- 目录题目及要求:提示:原创代码:代码思路:题目及要求:给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。提示:0 <