前言
在學(xué)習(xí) Flutter 中,我們了解到 Widget 有 StatelessWidget 和 StatefulWidget 兩種類型凭语。其中 StatefulWidget 對應(yīng)有交互楞黄、需要動態(tài)變化視圖效果的場景瓤荔,而 StatelessWidget 則用于處理靜態(tài)的奔坟、無狀態(tài)的視圖展示。下面就對 StatelessWidget 和 StatefulWidget 進(jìn)行學(xué)習(xí)和比較逛尚,從而更好地理解 Widget,掌握不同類型 Widget 的正確使用時(shí)機(jī)杠娱。
(一)UI 編程范式
首先了解一下在 Flutter 中乏奥,如何調(diào)整一個控件(Widget) 的展示樣式,即 UI 編程范式登渣。
在 Android噪服、iOS 或者 JavaScript 開發(fā)中,視圖開發(fā)是命令式的胜茧,需要精確地告訴操作系統(tǒng)或?yàn)g覽器用何種方式去做事情粘优。比如,如果我們想要變更界面的文案呻顽,則需要找到具體的文本控件并調(diào)用它的控件方法命令雹顺,才能完成文字變更。
示例廊遍,將文本控件的內(nèi)容更改為 “Hello World”:
// Android 設(shè)置某文本控件展示文案為 Hello World
TextView textView = (TextView) findViewById(R.id.txt);
textView.setText("Hello World");
// iOS 設(shè)置某文本控件展示文案為 Hello World
UILabel *label = (UILabel *)[self.view viewWithTag:1234];
label.text = @"Hello World";
// 原生 JavaScript 設(shè)置某文本控件展示文案為 Hello World
document.querySelector("#demo").innerHTML = "Hello World!";
與此不同的是嬉愧,Flutter 的視圖開發(fā)是聲明式的,其核心設(shè)計(jì)思想就是將視圖和數(shù)據(jù)分離喉前,這與 React 的設(shè)計(jì)思路完全一致没酣。
對于 Flutter 來說,如果要實(shí)現(xiàn)同樣的需求卵迂,除了設(shè)計(jì)好 Widget 布局方案之外裕便,還需要提前維護(hù)一套文案數(shù)據(jù)集,并為需要變化的 Widget 綁定數(shù)據(jù)集中的數(shù)據(jù)狭握,使 Widget 根據(jù)這個數(shù)據(jù)集完成渲染闪金。
這樣看似很麻煩,但是论颅,當(dāng)需要變更界面的文案時(shí)哎垦,我們只要改變數(shù)據(jù)集中的文案數(shù)據(jù),并通知 Flutter 框架觸發(fā) Widget 的重新渲染即可恃疯。比起命令式的視圖開發(fā)方式需要挨個設(shè)置不同組件(Widget)的視覺屬性漏设,這種方式要便捷得多。
總結(jié)來說今妄,命令式編程強(qiáng)調(diào)精準(zhǔn)控制過程細(xì)節(jié)郑口;而聲明式編程強(qiáng)調(diào)通過意圖輸出結(jié)果整體鸳碧。對應(yīng)到 Flutter 中,意圖是綁定了組件狀態(tài)的 State犬性,結(jié)果則是重新渲染后的組件瞻离。在 Widget 的生命周期內(nèi),應(yīng)用到 State 中的任何更改都將強(qiáng)制 Widget 重新構(gòu)建乒裆。
其中套利,對于組件完成創(chuàng)建后就無需變更的場景,狀態(tài)的綁定是可選項(xiàng)鹤耍。這里“可選”就區(qū)分出了 Widget 的兩種類型肉迫,即:StatelessWidget 不帶綁定狀態(tài),而 StatefulWidget 帶綁定狀態(tài)稿黄。當(dāng)你所要構(gòu)建的用戶界面不隨任何狀態(tài)信息的變化而變化時(shí)喊衫,需要選擇使用 StatelessWidget,反之則選用 StatefulWidget杆怕。前者一般用于靜態(tài)內(nèi)容的展示族购,而后者則用于存在交互反饋的內(nèi)容呈現(xiàn)中。
(二)StatelessWidget
在 Flutter 中财著,Widget 采用由父到子联四、自頂向下的方式進(jìn)行構(gòu)建撑碴,父 Widget 控制著子 Widget 的顯示樣式撑教,其樣式配置由父 Widget 在構(gòu)建時(shí)提供。
用這種方法構(gòu)建出的 Widget醉拓,有些(比如 Text伟姐、Container、Row亿卤、Column 等)在創(chuàng)建時(shí)愤兵,除了這些配置參數(shù)之外不依賴與任何其他信息,它們一旦創(chuàng)建成功就不再關(guān)心排吴、也不響應(yīng)任何數(shù)據(jù)變化進(jìn)行重繪秆乳。在 Flutter 中,這樣的 Widget 被稱為 StatelessWidget(無狀態(tài)組件)钻哩。
這里有一張 StatelessWidget 的示意圖屹堰,如下所示:
接下來,我以 Text 的部分源碼為例街氢,和你說明 StatelessWidget 的構(gòu)建過程扯键。
class Text extends StatelessWidget {
// 構(gòu)造方法及屬性聲明部分
const Text(this.data, {
Key key,
this.textAlign,
this.textDirection,
// 其他參數(shù)
...
}) : assert(data != null),
textSpan = null,
super(key: key);
final String data;
final TextAlign textAlign;
final TextDirection textDirection;
// 其他屬性
...
@override
Widget build(BuildContext context) {
...
Widget result = RichText(
// 初始化配置
...
)
);
...
return result;
}
}
可以看到,在構(gòu)造方法將其屬性列表賦值后珊肃,build 方法隨即將子組件 RichText 通過其屬性列表(如文本 data荣刑、對齊方式 textAlign馅笙、文本展示方向 textDirection 等)初始化后返回,之后 Text 內(nèi)部不再響應(yīng)外部數(shù)據(jù)的變化厉亏。
那么董习,什么場景下應(yīng)該使用 StatelessWidget 呢?
父 Widget 能夠通過初始化參數(shù)完全控制其 UI 展示效果爱只,那么就可以使用 StatelessWidget 來設(shè)計(jì)構(gòu)造函數(shù)接口了阱飘。
(三)StatefulWidget
在 Flutter 中有一些 Widget(比如 Image、Checkbox)的展示虱颗,除了父 Widget 初始化時(shí)傳入的靜態(tài)配置之外沥匈,還需要處理用戶的交互(比如,用戶點(diǎn)擊按鈕)或其內(nèi)部數(shù)據(jù)的變化(比如忘渔,網(wǎng)絡(luò)數(shù)據(jù)回包)高帖,并體現(xiàn)在 UI 上。這些 Widget 創(chuàng)建完成后畦粮,還需要關(guān)心和響應(yīng)數(shù)據(jù)變化來進(jìn)行重繪散址。在 Flutter 中,這類 Widget 被稱為 StatefulWidget(有狀態(tài)組件)宣赔。StatefulWidget 的示意圖预麸,如下所示:
接下來,我以 Image 的部分源碼為例儒将,和你說明 StatefulWidget 的構(gòu)建過程吏祸。
class Image extends StatefulWidget {
// 構(gòu)造方法及屬性聲明部分
const Image({
Key key,
@required this.image,
// 其他參數(shù)
}) : assert(image != null),
super(key: key);
final ImageProvider image;
// 其他屬性
...
@override
_ImageState createState() => _ImageState();
...
}
class _ImageState extends State<Image> {
ImageInfo _imageInfo;
// 其他屬性
...
void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
setState(() {
_imageInfo = imageInfo;
});
}
...
@override
Widget build(BuildContext context) {
final RawImage image = RawImage(
image: _imageInfo?.image,
// 其他初始化配置
...
);
return image;
}
...
}
同樣,Image 類的構(gòu)造函數(shù)會接受要被這個類使用的屬性參數(shù)钩蚊。不同的是贡翘,Image 類并沒有 build 方法來創(chuàng)建視圖,而是通過 createState 方法創(chuàng)建了一個類型為 _ImageState 的 state 對象砰逻,然后由這個對象負(fù)責(zé)視圖的構(gòu)建鸣驱。
拿 _imageInfo 屬性為例,_imageInfo 屬性用來給 Widget 加載真實(shí)的圖片蝠咆,一旦 State 對象通過 _handleImageChanged 方法監(jiān)聽到 _imageInfo 屬性發(fā)生變化踊东,就會立即調(diào)用 setState 方法通知 Flutter 框架重新構(gòu)建,更新 UI刚操。
在這個例子中闸翅,Image 以一種動態(tài)的方式運(yùn)行:監(jiān)聽變化,更新視圖赡茸。與 StatelessWidget 通過父 Widget 完全控制 UI 展示不同缎脾,StatefulWidget 的父 Widget 僅定義它的初始化狀態(tài),而自身視圖運(yùn)行的狀態(tài)則需要自己處理占卧,并根據(jù)處理情況即時(shí)更新 UI 展示遗菠。
(四)StatefulWidget 不是萬金油
這時(shí)我們會有疑問联喘,既然 StatefulWidget 不僅可以響應(yīng)狀態(tài)變化,又能展示靜態(tài) UI辙纬,那么 StatelessWidget 這種只能展示靜態(tài) UI 的 Widget豁遭,還有必要使用嗎?
首先贺拣,看一下 Widget 的更新機(jī)制:
Widget 是不可變的蓖谢,更新則意味著銷毀和重建。StatelessWidget 是靜態(tài)的譬涡,一旦創(chuàng)建則無需更新闪幽;而 StatefulWidget 在調(diào)用 setState 方法更新數(shù)據(jù)后,會觸發(fā)視圖的銷毀和重建涡匀,也將間接地觸發(fā)其每一個子 Widget 的銷毀和重建盯腌。
盡管 Flutter 會通過 Element 層最大程度降低對真實(shí)渲染視圖的修改,但是如果濫用 StatefulWidget陨瘩,在更新 UI 時(shí)腕够,一整個頁面所有的 Widget 都會銷毀和重建。這樣大大降低了渲染性能舌劳。
因此帚湘,正確評估視圖展示需求,避免無謂的 StatefulWidget 使用甚淡,是提高 Flutter 應(yīng)用渲染性能最簡單也是最直接的手段大诸。
總結(jié)
由于 Widget 采用由父到子、自頂向下的方式進(jìn)行構(gòu)建材诽,因此在自定義組件時(shí)底挫,我們可以根據(jù)父 Widget 是否能通過初始化參數(shù)完全控制其 UI 展示效果的基本原則,來選擇繼承 StatelessWidget 還是 StatefulWidget脸侥。