Stateless
與 Stateful
widget 是在建構頁面之前,你應該要先仔細思考該頁面是否會有 state
變化的產生而去選擇應該使用的類型。
在 Flutter 中,state
指的是 widget 的狀態或是儲存的資訊,可以藉由控制 state
變化藉此達到改變 widget 樣式、外觀或行為。所以stateless widget
也就代表此 widget 底下不需要進行 state
的處理;stateful widget
則表示需要處理 state
的狀態。以下我們將詳細的介紹
在開始之前,你可以先把 Flutter playground 替換成以下程式碼
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Flutter Tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Playground'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text('Hello World',
style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold)),
ElevatedButton(
onPressed: () {},
child: const Text('翻譯', style: TextStyle(color: Colors.white)))
])),
),
));
}
在分別的介紹兩者之間,讓我們先來針對前個章節只有引用,但可能不知道箇中細節的 widget
好好認識一番。在 Flutter 的官方定義中這麼說道:
A widget is an immutable decription of a part of a user interface.
從上面這句話我們得到幾個訊息:
所以一旦 widget 經過初始化後,便無法再改變。
StatelessWidget
表示該 widget 無狀態,通常用於顯示靜態內容,如:文字、圖片、按鈕等等。在我們這幾天建構的應用程式中,其實都是屬於 stateless widget 的範疇,原因是我們還沒有做任何關於互動相關的行為。
讓我們來寫第一個 stateless widget 吧!如果你跟我一樣使用的是 VS Code 並且有安裝插件的話,請在非 void main
函式區域輸入 st
的關鍵字,此時插件就跳出自動補全,其中一個選項為 Flutter Stateless Widget
,請選擇該選項。此時你的 VS Code 應該已經自動插入一段程式碼如下
// 類別名稱為 MyWidget,且該類別繼承 StatelessWidget
class MyWidget extends StatelessWidget {
// 類別建構子
const MyWidget({super.key});
// 由於 StatelessWidget 類別本身有 build 函式,這裡需要進行覆寫
// build 函式中的回傳值便是當該 Widget 被引用時會顯示的樣貌
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
我們將原先程式碼中 MaterialAPP
中的 home
參數承接的 Scaffold(…)
全數剪下並貼上至 MyWidget
中的 return
值。此時一旦建立 MyWidget
物件便是從 Scaffold
開始往下建構。
此時就 main()
函式便可以簡化成如下:
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
title: 'Flutter Tutorial',
home: MyWidget(),
);
}
如此我們就成功的將程式碼以 stateless widget 形式進行封裝。看起來很簡單對吧!
前面有說到使用 stateless widget 的時機為顯示靜態內容;相反的如果遇到動態的場景時,就需要 stateful widget
派上用場拉。
由於 Flutter 是藉由偵測 state
狀態是否更新來重新渲染,以達成更新畫面的目的,因此我們需要處理 state
的部分。
讓我們來改寫目前的程式碼。目前為止我們的應用程式執行結果有一個文字顯示「Hello World」 與其下方有一個「翻譯」的按鈕,點擊按鈕什麼事情都還不會發生。我希望可以改成當我點擊「翻譯」的時候,將上方顯示文字改變成「你好,世界」。顯然地,標題的文字顯示內容是可變動的。
因為牽涉到畫面的更新,所以很直觀的這需要透過 stateful widget
來解決。因此讓我們來將原先的 stateless widget
改成 stateful
的版本吧!
VS Code 插件支援快速將 stateless widget 改成 stateful widget 的功能,將滑鼠點至想要更新的 class 上方,會出現一個小燈泡,就可以進行選擇囉
同樣的我們也先來看看這段程式碼,並加入我們所需要狀態管理的變數 title
// 類別名稱為 MyWidget,且該類別繼承 StatefulWidget
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String title = 'Hello World';
@override
Widget build(BuildContext context) {
return ...省略
}
}
這段程式碼表示幾件事情,MyWidget 已改為繼承 StatefulWidget,並且建立了 state
物件,而 state
物件管理了變數 title
的狀態初始化為 Hello World
。
當然有時初始化的動作較複雜,如訂閱 Stream 等,在 State
類別中也提供另一個方法 initState()
可供初始化
class _MyWidgetState extends State<MyWidget> {
late String title;
@override
void initState() {
super.initState();
title = 'Hello World';
}
@override
Widget build(BuildContext context) {
return ...省略
}
}
剩下一步,也就是我們要在按鈕點按的時候更新 title
的值。如果要更改 State
的值,則需要透過 setState()
方法來通知 Flutter 說此 widget 的狀態發生了變化,需要重新的渲染。當 widget 需要重新渲染時,便會再一次的呼叫 build
函式。
請找到 button
中的 onPressed()
,並將以下程式碼插入至該函式
// 當有點按事件時,便會觸發此函式
onPressed: () {
// 需藉由 setState 方法來通知框架此 widget 的狀態有所更動。否則直接改值是不會有效果的
setState(() {
message = '你好,世界';
});
}
這麼一來,每當你點擊「翻譯」按鈕時,便可以成功的將標題替換成想要的文字拉!這也是我們完成的第一個 Stateful widget。
這時可能讀者會開始有疑問了。既然 stateful widget 可根據 state 的變動來決定是否重新 渲染該 widget,也就表示 stateful widget 比較有彈性,那我可不可以所有 widget 都宣告成 stateful widget 呢?
答案是可以的,但是並不建議你這麼做。理由是 stateful widget 在狀態更新時會將自身以及子身底下的子樹也進行重新渲染,當不適當的使用 setState
更新時很可能因過度的重新渲染而造成效能的減損。
因此當在選擇一個 widget 是否要 statueful / stateless 時,
其實今天的內容我很糾結是否要與昨天的內容進行順序調換,因為昨天是先教大家引用幾個基礎 widget,但沒有先介紹概念。
但最終仍做了這項決定的原因是希望至少先讓讀者知道 flutter 構建、引用 widget 的語法。然後把改寫成 stateless 與 stateful widget 的實作放在今天,希望有助於大家了解各自的寫法以及選擇應使用何者的使用場景。
今日的總結就一句話:請優先使用 stateless widget ,當有遇到需要記錄內部狀態時才考慮使用 stateful widget。
明天開始我們將要針對我們的專案進行前置作業的設定,我們將用到幾個工具來幫助我們更好的進行開發!