Flutter 中 StatelessWidget 與 StatefulWidget 的抉擇

前言

在學(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 的示意圖屹堰,如下所示:


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 的示意圖预麸,如下所示:


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脸侥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市盈厘,隨后出現(xiàn)的幾起案子睁枕,更是在濱河造成了極大的恐慌,老刑警劉巖沸手,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件外遇,死亡現(xiàn)場離奇詭異,居然都是意外死亡契吉,警方通過查閱死者的電腦和手機(jī)跳仿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捐晶,“玉大人菲语,你說我怎么就攤上這事妄辩。” “怎么了山上?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵眼耀,是天一觀的道長。 經(jīng)常有香客問我佩憾,道長哮伟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任妄帘,我火速辦了婚禮楞黄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抡驼。我一直安慰自己谅辣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布婶恼。 她就那樣靜靜地躺著桑阶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勾邦。 梳的紋絲不亂的頭發(fā)上蚣录,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音眷篇,去河邊找鬼萎河。 笑死,一個胖子當(dāng)著我的面吹牛蕉饼,可吹牛的內(nèi)容都是我干的虐杯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼昧港,長吁一口氣:“原來是場噩夢啊……” “哼擎椰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起创肥,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤达舒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后叹侄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巩搏,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年趾代,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贯底。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡撒强,死狀恐怖禽捆,靈堂內(nèi)的尸體忽然破棺而出笙什,到底是詐尸還是另有隱情,我是刑警寧澤睦擂,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布得湘,位于F島的核電站,受9級特大地震影響顿仇,放射性物質(zhì)發(fā)生泄漏淘正。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一臼闻、第九天 我趴在偏房一處隱蔽的房頂上張望鸿吆。 院中可真熱鬧,春花似錦述呐、人聲如沸惩淳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽思犁。三九已至,卻和暖如春进肯,著一層夾襖步出監(jiān)牢的瞬間激蹲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工江掩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留学辱,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓环形,卻偏偏與公主長得像策泣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抬吟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345