本章介紹 Flutter 的組件承粤,以及組件的生命周期沧奴。
一昭灵、組件 Widget定義
Flutter 中的組件與前端組件的理解和作用基本一致店展,但是沒有一個明確的概念解釋 Flutter 組件苛聘,這里借用前端的組件定義來解釋 Flutter 組件的概念涂炎。
一個 Flutter 組件,包含了組件的模板设哗、樣式和交互等內(nèi)容唱捣,外部只要按照組件設(shè)定的屬性、函數(shù)及事件處理等進行調(diào)用即可网梢,完全不用考慮組件的內(nèi)部實現(xiàn)邏輯震缭。其中組件又包括無狀態(tài)組件和有狀態(tài)組件。
- 無狀態(tài)組件
無狀態(tài)組件战虏,可以理解為將外部傳入的數(shù)據(jù)轉(zhuǎn)化為界面展示的內(nèi)容拣宰,只會渲染一次。
- 有狀態(tài)組件
有狀態(tài)組件烦感,是定義交互邏輯和業(yè)務(wù)數(shù)據(jù)巡社,可以理解為具有動態(tài)可交互的內(nèi)容界面,會根據(jù)數(shù)據(jù)的變化進行多次渲染啸盏。
二重贺、生命周期
在原生 Android 、原生 iOS 回懦、前端 React 或者 Vue 都存在生命周期的概念气笙,在 Flutter 中一樣存在生命周期的概念,其基本概念和作用相似怯晕。 Flutter 中說的生命周期潜圃,也是指有狀態(tài)組件,對于無狀態(tài)組件生命周期只有 build 這個過程舟茶,也只會渲染一次谭期,而有狀態(tài)組件則比較復(fù)雜,下面我們就來看看有狀態(tài)組件的生命周期過程吧凉。
1)隧出、生命周期的流轉(zhuǎn)
Flutter 中的生命周期,包含以下幾個階段:
createState 阀捅,該函數(shù)為 StatefulWidget 中創(chuàng)建 State 的方法胀瞪,當(dāng) StatefulWidget 被調(diào)用時會立即執(zhí)行 createState 。
initState ,該函數(shù)為 State 初始化調(diào)用凄诞,因此可以在此期間執(zhí)行 State 各變量的初始賦值圆雁,同時也可以在此期間與服務(wù)端交互,獲取服務(wù)端數(shù)據(jù)后調(diào)用 setState 來設(shè)置 State帆谍。
注意:這個方法是重寫父類的方法伪朽,必須調(diào)用super
,因為父類中會進行一些其他操作汛蝙;
并且如果你閱讀源碼烈涮,你會發(fā)現(xiàn)這里有一個注解(annotation):@mustCallSuper
@protected
@mustCallSuper
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
}
-
didChangeDependencies ,該函數(shù)是在該組件依賴的 State 發(fā)生變化時窖剑,這里說的 State 為全局 State 跃脊,例如語言或者主題等,類似于前端 Redux 存儲的 State 苛吱。這個方法在兩種情況下會調(diào)用:
情況一:調(diào)用initState會調(diào)用;
情況二:從其他對象中依賴一些數(shù)據(jù)發(fā)生改變時器瘪,比如前面我們提到的InheritedWidget翠储;
build ,主要是返回需要渲染的 Widget 橡疼,由于 build 會被調(diào)用多次援所,因此在該函數(shù)中只能做返回 Widget 相關(guān)邏輯,避免因為執(zhí)行多次導(dǎo)致狀態(tài)異常欣除。
reassemble 住拭,主要是提供開發(fā)階段使用,在 debug 模式下历帚,每次熱重載都會調(diào)用該函數(shù)滔岳,因此在 debug 階段可以在此期間增加一些 debug 代碼,來檢查代碼問題挽牢。
didUpdateWidget 谱煤,該函數(shù)主要是在組件重新構(gòu)建,比如說熱重載禽拔,父組件發(fā)生 build 的情況下刘离,子組件該方法才會被調(diào)用,其次該方法調(diào)用之后一定會再調(diào)用本組件中的 build 方法睹栖。
deactivate 硫惕,在組件被移除節(jié)點后會被調(diào)用,如果該組件被移除節(jié)點野来,然后未被插入到其他節(jié)點時恼除,則會繼續(xù)調(diào)用 dispose 永久移除。
dispose 梁只,永久移除組件缚柳,并釋放組件資源埃脏。
整個過程分為四個階段:
1.初始化階段,包括兩個生命周期函數(shù) createState 和 initState秋忙;
2.組件創(chuàng)建階段彩掐,也可以稱組件出生階段,包括 didChangeDependencies 和 build灰追;
3.觸發(fā)組件多次 build 堵幽,這個階段有可能是因為 didChangeDependencies、setState 或者 didUpdateWidget 而引發(fā)的組件重新 build 弹澎,在組件運行過程中會多次被觸發(fā)朴下,這也是優(yōu)化過程中需要著重需要注意的點;
4.最后是組件銷毀階段苦蒿,deactivate 和 dispose殴胧。
2)、組件首次加載執(zhí)行過程
我們先實現(xiàn)一段代碼佩迟,來看下組件在首次創(chuàng)建的執(zhí)行過程是否是按照圖 1 的流程团滥。
- 1、 在 lib 中 pages 下創(chuàng)建 test_stateful_widget.dart 报强;
- 2灸姊、 在 test_stateful_widget.dart 添加如下代碼:
import 'package:flutter/material.dart';
/// 創(chuàng)建有狀態(tài)測試組件
class TestStatefulWidget extends StatefulWidget {
@override
createState() {
print('create state');
return TestState();
}
}
/// 創(chuàng)建狀態(tài)管理類,繼承狀態(tài)測試組件
class TestState extends State<TestStatefulWidget> {
/// 定義 state [count] 計算器
int count = 1;
/// 定義 state [name] 為當(dāng)前描述字符串
String name = 'test';
@override
initState() {
print('init state');
super.initState();
}
@override
didChangeDependencies() {
print('did change dependencies');
super.didChangeDependencies();
}
@override
didUpdateWidget(TestStatefulWidget oldWidget) {
count++;
print('did update widget');
super.didUpdateWidget(oldWidget);
}
@override
deactivate() {
print('deactivate');
super.deactivate();
}
@override
dispose() {
print('dispose');
super.dispose();
}
@override
reassemble(){
print('reassemble');
super.reassemble();
}
/// 修改 state name
void changeName() {
setState(() {
print('set state');
this.name = 'flutter';
});
}
@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), // 使用 Text 組件顯示描述字符和當(dāng)前計算
onPressed:()=> this.changeName(), // 點擊觸發(fā)修改描述字符 state name
)
],
);
}
}
上述代碼把有狀態(tài)組件的一些生命周期函數(shù)都進行了重寫秉溉,并且在執(zhí)行中都打印了一些字符串標(biāo)識力惯,目的是可以看到該函數(shù)被執(zhí)行。
- 3召嘶、 然后在 main.dart 中加載該組件父晶,代碼如下:
import 'package:flutter/material.dart';
import 'package:flutter_liftcycle_demo/pages/test_stateful_widget.dart';
/// APP 核心入口文件
void main() => runApp(MyApp());
/// MyApp 核心入口界面
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Lifecycle', // APP 名字
theme: ThemeData(
primarySwatch: Colors.blue, // APP 主題
),
home: Scaffold(
appBar: AppBar(
title: Text('首頁'), // 頁面名字
),
body: Center(
child:
TestStatefulWidget(),
)
));
}
}
代碼修改后,我們打開手機模擬器弄跌,然后運行該 App 诱建,在輸出控制臺可以看到下面的運行打印日志信息。
flutter: create state
flutter: init state
flutter: did change dependencies
flutter: build
flutter: reassemble
flutter: did update widget
flutter: build
運行結(jié)果中碟绑,打印過程可以看到是按照我們上面圖 1 的執(zhí)行流程在運行的俺猿,但其中最值得關(guān)注的是 build 運行了兩次
。這是在開發(fā)模式下才會執(zhí)行的過程格仲,在正式環(huán)境是不會出現(xiàn)的押袍,因為重新渲染成本非常大,這個問題可以使用打印 build 的調(diào)用堆棧即可發(fā)現(xiàn)凯肋。如果你要關(guān)閉兩次 build 也可以實現(xiàn)谊惭,在 Flutter 框架中搜索 constants.dart 文件,并找到下面這行代碼,將 defaultValue 從 false 修改為 true圈盔。
const bool kReleaseMode = bool.fromEnvironment('dart.vm.product', defaultValue: true);
其實這里會觸發(fā) didUpdateWidget
函數(shù)豹芯,是因為 TestStatefulWidget 組件是 MyApp 組件中的子組件,從而導(dǎo)致 MyApp 函數(shù)中的 build 觸發(fā)子組件 didUpdateWidget 函數(shù)的執(zhí)行驱敲,具體會在下面觸發(fā)組件再次 build 中詳細說明铁蹈。
3)、觸發(fā)組件再次 build
觸發(fā)組件再次 build 有三種方式: 一個是 setState 众眨,另一個是 didChangeDependencies 握牧,再一個是 didUpdateWidget 。
-
setState
比較容易理解娩梨,在數(shù)據(jù)狀態(tài)進行變化時沿腰,觸發(fā)組件 build ,在上面的代碼運行后的界面中狈定,點擊中間的text颂龙,如圖 2 位置,就可以看到在調(diào)用 setState 后纽什,會調(diào)用 build 一個方法厘托。
//點擊中間的text
flutter: set state
flutter: build
didChangeDependencies
,你可以理解為本組件依賴的全局 state 的值發(fā)生了變化稿湿,例如前端的 redux 中的數(shù)據(jù)發(fā)生了變化,也會進行 build 操作押赊。一般情況下我們會將一些比較基礎(chǔ)的數(shù)據(jù)放到全局變量中饺藤,例如主題顏色、地區(qū)語言或者其他通用變量等流礁。如果這些全局 state 發(fā)生狀態(tài)變化則會觸發(fā)該函數(shù)涕俗,而該函數(shù)之后就會觸發(fā) build 操作。didUpdateWidget
觸發(fā) build 我們需要從代碼層面來講解下神帅,現(xiàn)在我們需要設(shè)計兩個組件再姑,一個是我們剛實現(xiàn)的 TestStatefulWidget ,另外一個則是該組件的子組件,我們命名為SubStatefulWidget 。接下來我們在 TestStatefulWidget 加載該組件既棺,在頭部 import 該組件滞伟,然后將 build 中的代碼修改為下面:
@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), // 使用 Text 組件顯示描述字符和當(dāng)前計算
onPressed:()=> this.changeName(), // 點擊觸發(fā)修改描述字符 state name
),
SubStatefulWidget() // 加載子組件
],
);
}
接下來我們實現(xiàn) SubStatefulWidget 子組件的代碼,和父組件基本相似翘狱,只是在打印處都加了 sub ,其次 build 實現(xiàn)邏輯也修改了,具體代碼如下:
import 'package:flutter/material.dart';
/// 創(chuàng)建子組件類
class SubStatefulWidget extends StatefulWidget {
@override
createState() {
print('sub create state');
return SubState();
}
}
/// 創(chuàng)建子組件狀態(tài)管理類
class SubState extends State<SubStatefulWidget> {
String name = 'sub test';
@override
initState() {
print('sub init state');
super.initState();
}
@override
didChangeDependencies() {
print('sub did change dependencies');
super.didChangeDependencies();
}
@override
didUpdateWidget(SubStatefulWidget oldWidget) {
print('sub did update widget');
super.didUpdateWidget(oldWidget);
}
@override
deactivate() {
print('sub deactivate');
super.deactivate();
}
@override
dispose() {
print('sub dispose');
super.dispose();
}
@override
reassemble(){
print('sub reassemble');
super.reassemble();
}
@override
Widget build(BuildContext context) {
print('sub build');
return Text('subname $name'); // 使用Text組件顯示當(dāng)前name state
}
}
代碼實現(xiàn)完成后遇革,我們再重新加載 App ,可以看到如下運行日志信息。
flutter: create state
flutter: init state
flutter: did change dependencies
flutter: build
flutter: sub create state
flutter: sub init state
flutter: sub did change dependencies
flutter: sub build
flutter: reassemble
flutter: sub reassemble
flutter: did update widget
flutter: build
flutter: sub did update widget
flutter: sub build
加載 TestStatefulWidget 組件萝快,四個狀態(tài)函數(shù) createState锻霎、initState、didChangeDependencies 和 build揪漩;
加載 SubStatefulWidget 組件旋恼,四個狀態(tài)函數(shù) createState、initState氢拥、didChangeDependencies 和 build蚌铜;
TestStatefulWidget 進行二次 build ,因為父組件需要重新 build 觸發(fā)子組件的 didUpdateWidget 嫩海,didUpdateWidget 則觸發(fā) build冬殃。
為了驗證上面邏輯,我們現(xiàn)在再次點擊圖 3 中的紅色部分叁怪,來觸發(fā) TestStatefulWidget 組件的 build 审葬,看下是否會觸發(fā)子組件的 didUpdateWidget 和 build。
在運行日志窗口可以看到增加了下面的日志信息奕谭。
flutter: set state
flutter: build
flutter: sub did update widget
flutter: sub build
這就說明了父組件的變化會引發(fā)子組件的 build 涣觉,雖然子組件沒有任何的改動。這點如果是在前端的話血柳,是需要使用 shouldUpdateComponent 官册,來介紹重新構(gòu)建,不過在 Flutter 中是沒有該功能來減少重新 build 的难捌。
4)膝宁、組件銷毀觸發(fā)
在上面的代碼基礎(chǔ)上,我們直接在 TestStatefulWidget 組件中注釋子組件 SubStatefulWidget 的調(diào)用根吁,然后熱重載
即可看到下面的日志信息(請注意一定是需要熱重載才會有效果员淫,主要目的是一開始加載了該組件,后面再去掉該組件觸發(fā))击敌。
flutter: reassemble
flutter: sub reassemble
flutter: did update widget
flutter: build
flutter: sub deactivate
flutter: sub dispose
5) 補充: 生命周期屬性
其實生命周期中initState方法前后還有兩個屬性調(diào)用介返,如下圖:
1、mounted是State內(nèi)部設(shè)置的一個屬性沃斤,事實上我們不了解它也可以圣蝎,但是如果你想深入了解它,會對State的機制理解更加清晰衡瓶;
- 很多資料沒有提到這個屬性捅彻,但是我這里把它列出來,是內(nèi)部設(shè)置的鞍陨,不需要我們手動進行修改步淹;
2从隆、dirty state的含義是臟的State
- 它實際是通過一個Element的東西的屬性來標(biāo)記的;
- 將它標(biāo)記為dirty會等待下一次的重繪檢查缭裆,強制調(diào)用build方法來構(gòu)建我們的Widget键闺;
3、clean state的含義是干凈的State
- 它表示當(dāng)前build出來的Widget澈驼,下一次重繪檢查時不需要重新build辛燥;