具狀態元件
瞭解 StatefulWidget 以及如何重新建置 Flutter UI。
瞭解元件何時需要具有狀態,以及如何使用 setState 觸發 UI 更新。
你將完成的事項
Steps
1
簡介
簡介
到目前為止,你的應用程式顯示了一個格狀方格和一個輸入欄位,但方格尚未更新以反映使用者的猜測。當此應用程式完成時,下一個未填滿列中的每個方塊,應在每次提交使用者猜測後更新,具體方式為:
- 顯示正確的字母。
- 變更顏色以反映字母是否正確(綠色)、字母存在於單詞中但位置錯誤(黃色),或字母完全不在單詞中(灰色)。
為了處理這種動態行為,你需要將 GamePage 從 StatelessWidget 轉換為 StatefulWidget。
2
為何需要具狀態元件?
為何需要具狀態元件?
當元件 (Widget) 的外觀或資料需要在其生命週期內改變時,你需要使用 StatefulWidget 以及配套的 State 物件。雖然 StatefulWidget
本身仍然是不可變的(其屬性在建立後無法更改),但 State 物件是長期存在的,可以保存可變的資料,並且當資料發生變化時可以重新建置,從而使 UI 更新。
例如,以下的元件樹展示了一個簡單的應用程式範例,該應用程式使用具狀態元件,其計數器會在按下按鈕時遞增。
以下是基本的 StatefulWidget 結構(目前尚未執行任何操作):
class ExampleWidget extends StatefulWidget {
ExampleWidget({super.key});
@override
State<ExampleWidget> createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
3
將 GamePage 轉換為具狀態元件
將 GamePage 轉換為具狀態元件
要將 GamePage(或任何其他)元件從無狀態元件轉換為具狀態元件,請執行以下步驟:
- 將
GamePage改為繼承StatefulWidget而非StatelessWidget。 -
建立一個名為
_GamePageState的新類別,繼承State<GamePage>。這個新類別將保存可變的狀態和build方法。將build方法以及所有在元件上實例化的屬性從GamePage移至狀態物件。 - 在
GamePage中實作createState()方法,該方法會回傳_GamePageState的實例。
修改後的程式碼應如下所示:
class GamePage extends StatefulWidget {
GamePage({super.key});
@override
State<GamePage> createState() => _GamePageState();
}
class _GamePageState extends State<GamePage> {
final Game _game = Game();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
for (var guess in _game.guesses)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var letter in guess)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.5, vertical: 2.5),
child: Tile(letter.char, letter.type),
)
],
),
GuessInput(
onSubmitGuess: (_) {
// TODO, handle guess
print(guess); // Temporary
},
),
],
),
);
}
}
4
使用 setState 更新 UI
使用 setState 更新 UI
每當你修改 State 物件時,你必須呼叫 setState
來通知框架更新使用者介面並再次呼叫 build 方法。
在此應用程式中,當使用者進行猜測時,猜測的單詞會儲存在 Game 物件上,該物件是 GamePage 類別的屬性,因此是可能會變更並需要 UI 更新的狀態 (state)。當此狀態被修改時,方格應重新繪製以顯示使用者的猜測。
要實作這一點,請更新傳遞給 GuessInput 的回呼(callback)函式。該函式需要呼叫 setState,並在 setState
內部執行邏輯以判斷使用者的猜測是否正確。
更新你的程式碼:
class GamePage extends StatefulWidget {
GamePage({super.key});
@override
State<GamePage> createState() => _GamePageState();
}
class _GamePageState extends State<GamePage> {
final Game _game = Game();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
for (var guess in _game.guesses)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var letter in guess)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.5, vertical: 2.5),
child: Tile(letter.char, letter.type),
)
],
),
GuessInput(
onSubmitGuess: (String guess) {
setState(() { // NEW
_game.guess(guess);
});
},
),
],
),
);
}
}
現在,當你在 TextInput 中輸入合法的猜測並提交時,應用程式將反映使用者的猜測。如果你在未呼叫 setState 的情況下呼叫 _game.guess(guess),內部遊戲資料將會改變,但 Flutter 不會知道需要重新繪製螢幕,使用者也不會看到任何更新。
5
將 GuessInput 轉換為具狀態元件
將 GuessInput 轉換為具狀態元件
當 GamePage 在呼叫 setState 後重新建置時,
它的所有子元件也會一併重新建置。
由於 GuessInput 最初是以 StatelessWidget 建立的,
它的 TextEditingController 和 FocusNode 直接存放在元件類別中。
每次重新建置都會建立新的元件實例,因此這些控制器每次都會被重新建立,
導致文字輸入欄位失去焦點,
也讓不再使用的控制器沒有被妥善釋放。
為了在每次猜測之間讓文字欄位保持焦點,
並妥善管理控制器的生命週期,
請將 GuessInput 轉換為 StatefulWidget:
- 將
GuessInput改為繼承StatefulWidget,而非StatelessWidget。 - 建立配套的
_GuessInputState類別,繼承State<GuessInput>。 -
將
_textEditingController、_focusNode、_onSubmit()和build()移至_GuessInputState。 - 實作
dispose()以清理_textEditingController和_focusNode。
修改後的 GuessInput 元件應如下所示:
class GuessInput extends StatefulWidget {
const GuessInput({super.key, required this.onSubmitGuess});
final void Function(String) onSubmitGuess;
@override
State<GuessInput> createState() => _GuessInputState();
}
class _GuessInputState extends State<GuessInput> {
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
void dispose() {
_textEditingController.dispose();
_focusNode.dispose();
super.dispose();
}
void _onSubmit() {
widget.onSubmitGuess(_textEditingController.text.trim());
_textEditingController.clear();
_focusNode.requestFocus();
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
maxLength: 5,
focusNode: _focusNode,
autofocus: true,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(35)),
),
),
controller: _textEditingController,
onSubmitted: (input) {
_onSubmit();
},
),
),
),
IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.arrow_circle_up),
onPressed: _onSubmit,
),
],
);
}
}
將 GuessInput 轉換為 StatefulWidget 後,
_GuessInputState 會在父元件重新建置時保持存在,
讓文字欄位在每次提交猜測後維持焦點,
並在元件被卸載時妥善釋放資源。
6
複習
複習
你完成的事項
以下是你在本課程中建置和學習的摘要。瞭解元件何時需要具有狀態
當元件的外觀或資料需要在其生命週期內改變時,你需要 StatefulWidget。元件本身保持不可變,但其配套的 State 物件保存可變資料並觸發重新建置。
將 GamePage 和 GuessInput 轉換為 StatefulWidget
你透過建立配套的 State 類別、將可變屬性和生命週期管理(例如 dispose)移至其中,以及實作 createState(),將
GamePage 和 GuessInput 重構為具狀態元件。IDE 的快速輔助支援可以自動化此轉換。
使用 setState 讓應用程式回應使用者輸入
呼叫 setState 會告訴 Flutter 重新建置元件的 UI。當使用者提交猜測時,你呼叫 setState 來更新遊戲狀態,方格會自動反映新資料,同時讓文字欄位保持焦點。你的應用程式現在真正具有互動性了!
7
自我測驗
自我測驗
Stateful Widgets Quiz
1 / 2-
當元件需要發出 HTTP 請求時。
Not quite.
HTTP 請求可以從任一種元件發出,但狀態變更需要 StatefulWidget。
-
當元件的外觀或資料需要在其生命週期內改變時。
That's right!
當 UI 必須回應隨時間發生的資料變更時,需要使用 StatefulWidget。
-
當元件擁有超過三個子元件時。
Not quite.
子元件的數量不決定元件是否具有狀態。
-
當元件位於元件樹的根部時。
Not quite.
根元件可以是無狀態的;是否具有狀態取決於資料是否在元件的生命週期內發生變更。
-
應用程式將崩潰並出現錯誤。
Not quite.
應用程式不會崩潰,但 UI 不會更新。
-
資料在內部發生變更,但 Flutter 不會重新建置 UI 以反映變更。
That's right!
不呼叫 setState 時,Flutter 不知道需要重新繪製,因此使用者不會看到更新。
-
Flutter 自動偵測到變更並重新建置 UI。
Not quite.
Flutter 需要 setState 才能知道何時重新建置;它不會自動偵測變更。
-
元件從元件樹中被移除。
Not quite.
元件依然存在;只是在沒有 setState 的情況下不會在視覺上更新。
Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-10-25. View source or report an issue.