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编程简单实现九宫格。分享给大家供大家参考,具体如下:实现的步骤1. 一个整体的容器部分。就是上图中包括整个图片项
- 这个进度条可以反映真实进度,并且完成百分比的文字时随着进度增加而移动的,所在位置也恰好是真实完成的百分比位置,效果如下:思路如下:第一部分是
- Jenkins是一个java开发的、开源的、非常好用持续集成的工具,它能帮我们实现自动化部署环境、测试、打包等等的工作,还可以在构建任务成功
- Android 7.0行为变更 FileUriExposedException解决方法当我们开发关于【在应用间共享文件】相关功能的时候,在A
- 所谓前人栽树,后人乘凉,在此感谢博主的贡献。 原文:边缘凹凸的卡劵效果先上效果图:我实现的效果和原博主实现的效果是不一样的,我是左右边缘凹凸
- 本文实例讲述了java版微信公众平台消息接口应用方法。分享给大家供大家参考,具体如下:微信公众平台现在推出自动回复消息接口,但是由于是接口内
- 在Springboot中默认的静态资源路径有:classpath:/METAINF/resources/,classpath:/resour
- 我们已经写了一些Java程序。之前的每个Java程序都被保存为一个文件,比如Test.java。随后,该程序被编译为Test.class。我
- spring在启动时会自己把bean(java组件)注册到ioc容器里,实现控制反转,在开发人员使用spring开发应用程序时,你是看不到n
- 自己写了一个Swap测试类,代码如下:swap不能交换原生数据类型以及字符串类型。public class Swap5{ public st
- 前言属于基础的面试问题,一定要能够回答全哦~一、继承Thread,重写run方法通过自定义一个类(这里起名为:MyThread),继承Thr
- 一、ArrayList简介在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:ArrayList底层是一段连
- maven scope provided和runtime例子maven常用的scope有compile,provided,runtime,t
- RSA算法是一种非对称加密算法,那么何为非对称加密算法呢?一般我们理解上的加密是这样子进行的:原文经过了一把钥匙(密钥)加密后变成了密文,然
- 本文实例讲述了C#计算字符串哈希值(MD5、SHA)的方法。分享给大家供大家参考。具体如下:一、关于本文本文中是一个类库,包括下面几个函数:
- 概述对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度;| 类型 | 32位JVM | 6
- 前言:使用 interrupt 来通知线程停止运行,而不是强制停止!普通情况停止线程public class Right
- 原生系统Android8.1上,WiFi上出现感叹号,此时WiFi可正常访问。原因这是Android 5.0引入的网络评估机制:就是当你连上
- 有时安全不得不考虑,看看新闻泄漏风波事件就知道了我们在用Spring boot进行开发时,经常要配置很多外置参数ftp、数据库连接信息、支付
- 一. Dispatchers.IO1.Dispatchers.IO在协程中,当需要执行IO任务时,会在上下文中指定Dispatchers.I