Flutter開發(fā)setState能否在build中直接調(diào)用詳解
兩種情況
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)行原理。
以上就是Flutter開發(fā)setState能否在build中直接調(diào)用詳解的詳細(xì)內(nèi)容,更多關(guān)于Flutter setState調(diào)用build的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實現(xiàn)水波紋擴(kuò)散效果的實例代碼
這篇文章主要介紹了Android實現(xiàn)水波紋擴(kuò)散效果的實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
Android Studio創(chuàng)建AIDL文件并實現(xiàn)進(jìn)程間通訊實例
本篇文章主要介紹了Android Studio創(chuàng)建AIDL文件并實現(xiàn)進(jìn)程間通訊實例,具有一定的參考價值,有興趣可以了解一下。2017-04-04
Flutter實現(xiàn)倒計時秒數(shù)轉(zhuǎn)時分秒然后倒計時功能
有一個需求,需要在頁面進(jìn)行顯示倒計時,倒計時結(jié)束后,做相應(yīng)的邏輯處理,這篇文章主要介紹了Flutter實現(xiàn)倒計時功能,秒數(shù)轉(zhuǎn)時分秒,然后倒計時,需要的朋友可以參考下2023-08-08
android 復(fù)制 粘貼 剪切功能應(yīng)用
網(wǎng)上有很多android 復(fù)制 粘貼 剪切功能的文章,只是放到自己的程序中不知道如何處理,現(xiàn)在尋得一可行方法,需要的朋友可以參考下2012-11-11
android底部彈出iOS7風(fēng)格對話選項框(QQ對話框)--第三方開源之IOS_Dialog_Library
這篇文章主要介紹了android底部彈出iOS7風(fēng)格對話選項框(QQ對話框)--第三方開源--IOS_Dialog_Library的相關(guān)資料,需要的朋友可以參考下2015-11-11
Android實現(xiàn)3秒鐘自動關(guān)閉界面
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)3秒鐘自動關(guān)閉界面,以支付成功為例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02
Kotlin遍歷集合導(dǎo)致并發(fā)修改異常的原因和解決方法
這篇文章主要介紹了Kotlin遍歷集合導(dǎo)致并發(fā)修改異常的原因和解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Android 幾種屏幕間跳轉(zhuǎn)的跳轉(zhuǎn)Intent Bundle
這篇文章主要介紹了Android 幾種屏幕間跳轉(zhuǎn)的跳轉(zhuǎn)Intent Bundle,有需要的朋友可以參考一下2013-12-12

