Flutter 入門與實戰(三十八):從相親來看有狀態和無狀態元件|8月更文挑戰

島上碼農發表於2021-08-01

楔子

雷思是一名程式設計師,年齡老大不小了,家裡急啊,老催他相親。可是雷思同學對相親不太上心,相親網站應付似的填了點東西,然後截個圖給他老孃發過去,說是已經在相親網站掛出去了。其實他壓根就沒當回事——程式設計師嘛,哪裡會缺物件,現在用了 Dart 語言,連 new 都不需要了。 這一天,雷思正在找 bug,剛好定位到一個物件的記憶體分配問題,正準備動手改呢,叮咚,手機彈了一個訊息:“雷思,你好,小芙給你傳送了一個見面邀請,是否接受?”雷思正準備劃掉訊息,瞥了一眼頭像,突然有點心動,點了個“接受”按鈕。於是,雷思開始了他的第一次網上相親之旅。

相親的準備

小芙十分注重個人形象,而且又十分善於掩藏自己的內心狀態。從表面看她和普通的女孩子一樣,但是內心戲卻很足。

class Xiaofu extends StatefulWidget {
  Xiaofu({Key key}) : super(key: key) {
    print('constructor: 小芙');
  }

  @override
  _XiaofuState createState() => _XiaofuState();
}

class _XiaofuState extends State<Xiaofu> {
  //這是小芙的內心戲,表面看不到
}
複製程式碼

相反,程式設計師雷思很簡單,標準的單純技術男。

//簡簡單單的程式設計師——雷思
class Leisi extends StatelessWidget {
  Leisi({Key key}) : super(key: key) {
  	print('constructor: 雷思');
  }
}
複製程式碼

約定好的相親時間還有三個小時,小芙就開始梳妝打扮起來,這雷思可是她從好多簡歷裡挑出來的,必須精心準備。她始終記得媽媽教導過——女人面對男人時得優雅一點。

@override
void initState() {
  super.initState(); //媽媽教導過的話
  print('initState:小芙花了2小時化妝');
}
複製程式碼

這個時候呢,雷思還在寫程式碼。 image.png

相親的開場白

相親時間到了,雷思秉承著從國外名著學到的紳士習慣,提前了15分鐘達到了約定的餐廳——這地點是相親網站給他們推薦的。餐廳看著挺不錯,估計消費不低。這肯定是相親網站的合作餐廳,估計能給他們返不少點——雷思的網際網路平臺思維下意識就上來了。而小芙呢,自然是要等雷思到了之後才會出現。

@override
void didUpdateWidget(oldWidget) {
  super.didUpdateWidget(oldWidget);
  print('didUpdateWidget:小芙出現了');
}
複製程式碼

“你好!不好意思,讓你久等了!”小芙面帶微笑優雅地揮手向雷思打招呼。 雷思沒感覺到她的不好意思,只是突然有些侷促,“你……你好!呃,沒事,我也才到一會。” 小芙心裡飄過三個字——沒經驗,當然,這些雷思看不出來。

相親的過程

見了面了,招呼也打了,開始點菜吧。 “你來點吧!”雷思繼續遵循他的紳士風度,女士優先嘛! “你來吧,我也不懂吃些什麼。”小芙自然禮貌性地推託一下。 “呃,好吧!你喜歡吃什麼?” “隨便吧,我對吃沒什麼特別講究。” “那我就隨便點了啊。”雷思沒有注意到小芙的表情表了,真的隨便點了兩個菜。

@override
Widget build(BuildContext context) {
  print('build:雷思');
  return Center(
    child: Text('雷思隨便點了兩個菜'),
  );
}
複製程式碼
@override
Widget build(BuildContext context) {
  print('build:小芙');
  return Center(
    child: Column(children: [
      Text('表情:$_face'),
      TextButton(
          onPressed: () {
            setState(() {
              _face = '失望';
              print('小芙的表情變了');
            });
          },
          child: Text('改變表情')),
    ]),
  );
}
複製程式碼

image.png 接下來的過程就有點無聊了,結果自然是吃完各回各家。

結局

“相親也就這樣吧。”雷思沒多想,回家洗個澡,翻了會掘金的沸點——這個比相親有趣多了! 而小芙回到家,失望之情依然沒有消散。她想不明白怎麼就一時腦熱要決定和這個叫雷思的人相親,程式設計師真的像外界傳言的那樣無趣(簡單)!過了相當一會,才平復自己的懊惱的情緒。

@override
void deactivate() {
  print('deactivate:小芙的情緒平復了');
  super.deactivate();
}
複製程式碼

正當小芙準備將雷思從關注列表刪除時,她突然想明白了當時為什麼會決定和雷思相親,應該是這個名字和她喜歡的蕾絲一樣的讀音吧。她點了一下“取消關注”,就這樣,雷思從她的關注列表中消失了。

@override
void dispose() {
  print('dispose:小芙取消關注雷思');
  super.dispose();
}
複製程式碼

後記

從雷思和小芙的相親故事可以看到,雷思(StatelessWidget)作為無狀態元件,真的很簡單。而小芙(StatefulWidget)作為有狀態元件,內心戲十足。我們以一個演示頁面為例。

class StateDemoPage extends StatelessWidget {
  StateDemoPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('雷思和小芙的故事'),
      ),
      body: Column(
        children: [
          Xiaofu(),
          Leisi(),
        ],
      ),
    );
  }
}
複製程式碼

首次進入該頁面時,列印出來的內容如下,可以看到小芙多了2個步驟,分別是initStatedidChangeDependencies。也就是有狀態元件會多經歷兩個步驟:

  • initState:初始化狀態,在物件插入到元件樹後呼叫。而在元件的狀態建立和初始化狀態之間,框架會給狀態繫結一個 BuildContext。通常我們會在這裡請求網路或其他介面所需要的資料。
  • didChangeDependencies:當狀態依賴的物件改變的時候被呼叫(例如一個 InheritedWidget 被改變時),當 initState 完成後(即初始化完成後)會馬上呼叫該方法。該方法呼叫後會呼叫 build 重新構建元件樹。因為每次狀態依賴改變時都會呼叫 build 方法,因此通常不需要子類過載該方法,除非是有些負荷過重的任務(如單次的網路請求)不想每次build 時呼叫。
flutter: constructor: 小芙
flutter: constructor: 雷思
flutter: initState:小芙花了2小時化妝
flutter: didChangeDependencies:小芙
flutter: build:小芙
flutter: build:雷思
複製程式碼

之後就是 build 方法了,這個在無狀態和有狀態元件都有。在有狀態元件中,當初始化完成或呼叫setState如小芙的表情改變)會進行呼叫。

對於無狀態元件,後面就沒有別的方法了,但是對於有狀態元件還存在四個方法:

  • reassemble:重灌時呼叫,這個一般是在熱過載的時候(我們修改完程式碼,直接儲存後介面會隨之更新)。熱過載會重新構建元件,但不會初始化狀態。只是會在構建完成後呼叫 didUpdateWidget,告知元件更新完成。
  • didUpdateWidget:當元件配置更改時呼叫。父元件重建時會呼叫樹中的有狀態的子元件的該方法。同時會顯示一個新的同型別元件和 key。框架會更新元件的狀態指向新的元件,然後呼叫該方法,並把舊元件傳遞給該方法。該方法呼叫後總是會呼叫 build 方法。通常在該方法中完成元件移除的動作,例如動畫。
flutter: reassemble:小芙
flutter: constructor: 小芙
flutter: constructor: 雷思
flutter: didUpdateWidget:小芙出現了
flutter: build:小芙
flutter: build:雷思
flutter: deactivate:小芙的情緒恢復了
flutter: dispose:小芙將雷思從關注列表移除了
複製程式碼
  • deactivate:元件從元件樹移除時會被呼叫,當然有時候元件被移除後可能被重新插入到元件的別的位置,這時候會呼叫build 方法重新構建元件樹。如果完全從元件樹移除,之後就會呼叫 dispose 銷燬元件。在該方法中可以解除大部分物件的引用,從而釋放資源,直到 dispose 呼叫後釋放所有資源。
  • dispose:當有狀態元件再也不會呼叫 build時呼叫該方法(通常是退出頁面),在這裡可以消除一些引用物件(如定時器,尚未結束的動畫)來釋放資源。下圖展示了有狀態元件的狀態切換過程。
stateDiagram-v2
[*] --> Constructor
Constructor --> createState

createState --> didChangeDependencies
didChangeDependencies --> build
build --> deactivate: 退出
build-->build: setState
build-->reassemble: 熱過載
reassemble-->重新構建(constructor)
重新構建(constructor)-->didUpdateWidget
didUpdateWidget-->build
deactivate --> dispose
dispose --> [*]

從有狀態和無狀態元件的對比來看,有狀態元件要維護的生命週期函式多好幾個,效能上自然會消耗更多資源,因此如果沒有必要,推薦儘量使用無狀態元件。接下來的篇章我們看看元件真正的渲染過程。


我是島上碼農,微信公眾號同名。這是Flutter 入門與實戰的專欄文章。

??:覺得有收穫請點個贊鼓勵一下!

?:收藏文章,方便回看哦!

?:評論交流,互相進步!

相關文章