網(wǎng)站首頁 編程語言 正文
兩種情況
setState() 能在 build() 中直接調(diào)用嗎?答案是能也不能。
來看一段簡單的代碼:
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') 顯示結(jié)果是 1,看來 build() 調(diào)用 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 中去調(diào)用。運(yùn)行起來,結(jié)果出現(xiàn)報錯了:The following assertion was thrown building Builder(dirty): setState() or markNeedsBuild() called during build.
提示在 Builder 的 build() 過程中出現(xiàn)了斷言錯誤:build() 中不能調(diào)用 setState() 或 markNeedsBuild()。
這是什么情況呢,為什么第一種情況下可以在 build() 中調(diào)用 setState() 而第二種情況不行?下面來簡單地分析下其中包含的原理。
原理分析
先說一下結(jié)論,在 build() 中直接調(diào)用 setState() 要滿足一個前提條件:
如果當(dāng)前有組件 A 處于 build() 中,那么 setState() 引起 rebuild 的組件必須是 A 或者 A 的子孫組件,不能是 A 的祖先組件。
這是因為組件 build 的順序是從父到子,如果在子組件 build 的過程中執(zhí)行 setState() 之類會引起父組件的重新 build 那就死循環(huán)肯定是不行的。
接下來看下 Flutter 源碼中是如何判斷和控制的。setState() 的內(nèi)部會調(diào)用 _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
就是當(dāng)前正處于 build 狀態(tài)的 element。_debugCurrentBuildTarget()
的內(nèi)容如下:
bool _debugIsInScope(Element target) { Element? current = this; while (current != null) { if (target == current) { return true; } current = current._parent; } return false; }
_debugIsInScope() 中的 this
就是調(diào)用 setState() 會引起 rebuild 的組件,target
就是當(dāng)前正處于 build 的組件。其中的 while
循環(huán)會逐步比對 current 及其父組件是否當(dāng)前 build 的對象,找到了才會返回 true,否則就是 false。如果是 false,則后面的斷言就會出現(xiàn)錯誤:setState() or markNeedsBuild() called during build.
如果當(dāng)前有組件正在 build 那么決不能引起父組件的 rebuild,我們來看下前面舉例報錯的第二種情況。Builder 是 TestPage 的子組件,Builder 的 builder 方法里調(diào)用的 setState 是 TestPage 上的,也就是在子組件的 build 過程中使父組件 rebuild 了,那么就會引起斷言失敗;而第一種情況下是在 TestPage 的 build 過程中調(diào)用 setState 使自己重新 rebuild,可以滿足結(jié)論的前提,所以是可以調(diào)用的。
這里我們可以接著想下在第一種情況下,組件自己的 build 過程中調(diào)用了 setState 引起了自己重新 rebuild 的時候不是也會死循環(huán)了嗎?我們接著看下 markNeedsBuild()
的后半部分代碼,如果斷言成功后后面的邏輯:
void markNeedsBuild() { // ... // 前半部分是斷言。 if (dirty) { return; } _dirty = true; owner!.scheduleBuildFor(this); }
這里可以看到組件在 build 過程中 markNeedsBuild()
會使組件變?yōu)?dirty 狀態(tài),這個時候在 build 中直接調(diào)用 setState 后發(fā)現(xiàn)已經(jīng)是 dirty 狀態(tài)后會直接返回,而不會調(diào)度重新 build,所以就沒有問題了。
總結(jié)
通過以上的分析我們知道了 Flutter 是如何判斷如果在 build 過程中直接調(diào)用 setState 是否合法的。當(dāng)然我們在寫代碼的時候是不會在 build() 中直接調(diào)用 setState 的,了解以上過程更有助于我們排查問題和學(xué)習(xí) Flutter 的運(yùn)行原理。
原文鏈接:https://juejin.cn/post/7153186376433795108
相關(guān)推薦
- 2022-04-11 C#實現(xiàn)簡單的計算器小程序_C#教程
- 2022-02-25 git提交到本地倉庫了,但是分支卻推送不上去怎么回退到提交之前的狀態(tài)
- 2022-07-11 docker搭建redis 主從哨兵集群
- 2022-04-05 debian:根據(jù)文件名稱,找到對應(yīng)的包
- 2022-06-12 golang的協(xié)程上下文的具體使用_Golang
- 2022-06-11 C#實現(xiàn)文件Move和Copy操作_C#教程
- 2022-10-01 Python?Color類與文字繪制零基礎(chǔ)掌握_python
- 2022-08-02 python機(jī)器學(xué)習(xí)Logistic回歸原理推導(dǎo)_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支