两种情况

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的资料请关注Devmax其它相关文章!

Flutter开发setState能否在build中直接调用详解的更多相关文章

  1. ios – Xcode 5持续集成CodeSign失败

    嗨,您好.我正在尝试使用xcode5和OSXServer为我的iOS应用程序配置持续集成.我将证书和p12添加到系统KeyChain,我还将配置文件复制到配置文件的服务器文件夹.集成失败,日志显示错误消息短消息:Command/usr/bin/codesignFailedwithexitcode1完整信息:CodeSign/Library/Server/Xcode/Data/BotRuns/Cac

  2. ios – Swift 2.0 – Google Analytics事件构建器错误 – NSMutableDictionary无法转换为[NSObject:AnyObject]

    完美地使用Swift1.2代码:错误:任何的想法?解决方法你错了你的事件.这是正确的方法和一种方法.将事件跟踪器初始化为NSObject在发送方法中将NSObject转换为Anyobject.

  3. ios – 如何存档包含自定义框架的应用程序?

    我有一个我创建的xcode框架项目,我可以编译成一个myframework.framework文件.编译之后,我将这个框架拖到我应用程序的Frameworks项目文件夹中,然后利用框架中的类,将适当的import语句添加到需要它的任何类;这允许我的应用程序成功编译与在框架中定义的类的引用.要使应用程序成功部署到我的设备,我还将我的自定义框架添加到我的目标的“嵌入式二进制文件”部分.有了这一切,我可

  4. ios – 1个用于体系结构x86_64的重复符号

    我不确定我做错了什么.我将项目文件夹移动到另一个文件夹,并将备份文件夹复制到桌面.我试图打开备份项目并构建,我收到了链接器错误.所以我决定删除备份文件夹并将项目文件夹移回桌面.我不能再编译并收到以下错误.没有派生数据文件夹还是一样1.我打开了DerivedData文件夹并删除了其中的所有内容2.我删除了所有模拟器中的所有项目3.我重新启动了xCode,Clean和build还是一样将项目添加到Gi

  5. ios – 链接错误/ Build / Products / Debug-iphonesimulator文件未找到

    我有Xcode的问题,不断给我一个链接器错误,它不构建项目.以下是我得到的错误ld:filenotfound:/Users/someuser/Library/Developer/Xcode/DerivedData/MAR-akbwbarcniqxytctbebckkelkbxs/Build/Products/Debug-iphonesimulator/MAR.app/MARclang:error:

  6. iOS 64位兼容性

    我的构建设置如下所示:我的部署目标是iOS6.0.我只需要确认该应用程序是64位兼容的,我都是iOS新手,不久前接管了一个相当大的项目,所以我宁愿要求100%肯定.只是为了使事情变得更清楚,你如何确保iOS应用程序是64位兼容的?

  7. xcode – 苹果LLVM编译3.1错误

    只需更新Xcode到4.3,现在我无法建立我的应用程序.我收到这个错误:有人有这个问题吗?

  8. RubyMotion Build:错误!找不到名为`(?-mix:iOS Team Provisioning Profile)’的配置配置文件

    我正在尝试构建一个简单的RubyMotion应用程序,以便我可以在我的iPhone上传输.我执行了rakebuild:device关于这个错误的任何想法?你应该做这个:InordertosuccessfullyrunaRubyMotionapponanon-jailbrokeniPhone3GSIhadto:DeletealloftheexpiredprovisioningprofilesintheMacOSXKeychainAccessapp.Yeah,mypastislitteredwithmanyr

  9. ios – Apple Mach-O Linker&amp;Ditto Error – Xcode 8

    解决方法>退出Xcode>重新启动系统>选择Xcode–>优先.这将打开一个弹出窗口.选择“位置”.在位置中,您将看到“派生数据”.点击路径旁边的箭头图标.这将打开一个包含“派生数据”的文件夹,将其删除.>清洁产品并运行

  10. xcode – 在Mac上使用OpenGL ES功能

    我试图将opengl绘制到2d空间,并且正在执行以下操作,但它不会编译:我已经包含了OpenGL.framework框架,编译器跟踪说明如下.我已经没有关于如何解决它的想法.我的目标是目前的桌面应用程序,但我的目标是最终制作一个iPhone应用程序.解决方法你有没有包含适当的标题?在Mac上,这些是可能在iPhone上他们是另外,如果我没弄错的话,glOrthof()是特定于OpenGL-ES的.您可能需要在Mac上使用glOrtho().

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部