Flutter开发setState能否在build中直接调用详解

目录
  • 两种情况
  • 原理分析
  • 总结

两种情况

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&lt;TestPage&gt; {
  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&lt;DiagnosticsNode&gt; information = &lt;DiagnosticsNode&gt;[
          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,所以就没有问题了。

总结

通过以上的分析我们知道了 Flutter 是如何判断如果在 build 过程中直接调用 setState 是否合法的。当然我们在写代码的时候是不会在 build() 中直接调用 setState 的,了解以上过程更有助于我们排查问题和学习 Flutter 的运行原理。

以上就是Flutter开发setState能否在build中直接调用详解的详细内容,更多关于Flutter setState调用build的资料请关注我们其它相关文章!

(0)

相关推荐

  • Flutter改变状态变量是否必须写在setState回调详解

    正文 我们都知道 setState(VoidCallback fn) 是这样用的: setState(() { count++; }); 执行完后组件会重新 build(),就可以取到 count 的最新值了.但其实这样写也是一样的: count++; setState(() {}); 因为 setState() 最后会调用 markNeedsBuild(),Flutter 会调度使组件 rebuild,所以状态变量的改变不是必须写在 setState() 的回调里面,只需要最后执行一下 set

  • Flutter中使用setState时的6个简单技巧总结

    目录 前言 setState 有什么用? 技巧 1:保持## widgets小! 技巧 2:不要在构建方法中调用 setState 技巧 3:不要在 initState 方法中调用 setState 技巧4:setState()和setState(...)是相等的 技巧 5:setState(...) 代码必须很小 技巧 6:setState(...) 代码不能是异步的 结束 前言 setState 函数是在 Flutter 应用程序中管理状态的最基本方法.以下是一些保持应用可维护性的最佳实践.

  • Flutter开发setState能否在build中直接调用详解

    目录 两种情况 原理分析 总结 两种情况 setState() 能在 build() 中直接调用吗?答案是能也不能. 来看一段简单的代码: import 'package:flutter/material.dart'; class TestPage extends StatefulWidget { const TestPage({super.key}); @override State<TestPage> createState() => _State(); } class _State

  • 使用Flutter开发的抖音国际版实例代码详解

    简介 最近花了两天时间研究使用Flutter开发一个抖音国际版. 个人感觉使用Flutter开发app快得不要不要的额. 两天就基本可以开发个大概出来. 最主要是热重载,太方便实时调整UI布局了. 相应速度极快. 如下图: 主要项目架构 详细说明一下,开发主要在lib文件夹 pubspec.yaml是配置插件的位置,如http: ^0.12.0+4,类似依赖组件. common文件夹存放的是重写的网络组件,以及图标组件icons.dart config文件夹存放的api.dart,wei调用的a

  • Flutter实现自定义下拉选择框的示例详解

    在一些列表页面中,我们经常会有上方筛选项的的需求,点击出现一个下拉菜单,多选.单选.列表选等,而在Flutter中,并没有现成的这样的组件,找第三方的扩展有时候又会受到一定限制,所以最好我们可以自己做一个,这样即使扩展我们也会得心应手. 先看效果图: 关键点:弹出.收回动画.状态改变.选项联动 思路: 我们可以看到一个完整的下拉框有头部和具体的下拉选项两部分组成,头部又和下拉组进行了联动, 把头部当做1个数组,下方选项作为1个数组,两个数组数量一致之间形成一个完整的下拉选择框可以更好的控制联动效

  • Eclipse中导入Maven Web项目并配置其在Tomcat中运行图文详解

    今天因为实习的关系需要讲公司已经开发的项目导入进Eclipse,而公司的项目是用Maven来构建的所以,需要将Maven项目导入进Eclipse下. 自己因为没有什么经验所以搞了得两个多小时,在这里和大家分享一下自己的经验已经在这之中遇到的一些问题. 首先我通过svn将公司的项目checkout到了本地. 因为Maven遵循的是规约比配置重要的原则,所以Maven项目的结构一般是进入目录后是一个pom.xml文件和一个src文件夹,当然可能还存在一些README之类的这些都不重要,最关键的就是p

  • redis配置文件中常用配置详解

    此次安装的版本为: 5.0.3 [root@localhost local]# redis-server --version Redis server v=5.0.3 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=afabdecde61000c3 打开redis.cof NETWORK # 指定 redis 只接收来自于该IP地址的请求,如果不进行设置,那么将处理所有请求 bind 127.0.0.1 #是否开启保护模式,默认开启.要是配置

  • Webpack中publicPath使用详解

    目录 output output.path output.publicPath webpack-dev-server中的publicPath html-webpack-plugin template filename 最后 斜杠/的含义 参考 最近自己在搭建一个基于webpack的react项目,遇到关于output.publicPath和webpack-dev-server中publicPath的问题,而官方文档对它们的描述也不是很清楚,所以自己研究了下并写下本文记录. output outp

  • Mybatis开发要点-resultType和resultMap有什么区别详解

    目录 一.resultType 1.resultType介绍 2.映射规则 3.自动映射注意事项 4.代码演示 1.t_user_test.sql准备 2.实体类 3.Mapper接口类 4.Mapper xml 5.配置文件 6.启动测试类 7.执行结果 二.resultMap 1.resultMap  介绍 2.resultMap属性 3.使用场景 4.resultMap子元素属性 5.代码演示 1.mapper接口 2.Mapper.xml 3.启动测试 4.执行结果 三.结论 Mybat

  • Flutter网络请求Dio库的使用及封装详解

    目录 一.项目目录结构 二.封装思路: 三.添加依赖 四.简单实现网络请求 五.实现登录注册服务 六.使用service服务 Dart语言内置的HttpClient实现了基本的网络请求相关的操作.但HttpClient本身功能较弱,很多网络请求常用功能都不支持,因此在实际项目中,我们更多是使用dio库实现网络请求. 注:Flutter官网同样推荐在项目中使用Dio库. Dio文档地址: pub.dev地址:dio | Dart Package 一.项目目录结构 文件夹 功能 components

  • npm脚本库组织在项目中的地位详解

    目录 一.脚本的地位 二.“脚本调度”的难题 三.如此简单? 四.此剑名曰: npm-run-all 4.1 安装 4.2 第一个命令: npm-run-all 4.3 第二个命令:npm-s 4.4 第三个命令:npm-p 4.5 通配符 4.6 更多实用能力 一.脚本的地位 脚本是 项目真正的入口 . 无论你是刚刚 clone 完公司的项目,抑或是你准备在开源社区做一点微小的贡献:你需要做的第一件事,永远是: 打开 package.json,看看 scripts 里都有哪些脚本. 有些脚本负

  • Classloader隔离技术在业务监控中的应用详解

    目录 1. 背景&简介 2. 业务监控平台脚本调试流程 2.1 业务监控的脚本开发调试流程图 3. 自定义Classloder | 打破双亲委派 3.1 什么是Classloader 3.2 Classloader动态加载依赖文件 3.3 自定义类加载器 3.4 业务监控使用CustomClassloader 3.5 业务监控动态加载JAR和脚本的实现 4. 问题&原因&方案 5. 总结 1. 背景&简介 业务监控平台是得物自研的一款用于数据和状态验证的平台.能快速便捷发现

随机推荐