往期相關(guān)內(nèi)容
- Flutter State Management狀態(tài)管理全面分析:http://www.itdecent.cn/p/9334b8f68004
- Flutter Provider 迄今為止最深、最全、最新的源碼分析:https://juejin.im/post/6844904176074358791
- Flutter之萬(wàn)物皆Widget:https://juejin.im/post/6873323337016311816
前言
在上期,我們手寫了一個(gè)Widget的實(shí)現(xiàn),并接觸到了Element,經(jīng)過(guò)一系列的分析,我們對(duì)Widget和Element的認(rèn)識(shí)更進(jìn)一步,那么這期我們就來(lái)深入理解下State,相信大家在開發(fā)過(guò)程中,總會(huì)用到StatefulWidget,那么官方為什么設(shè)計(jì)一個(gè)含有State的Widget?State生命周期是怎么來(lái)的?為什么State可以更新UI?帶著一些疑問(wèn),我們不直接分析源碼,而是手寫一個(gè)帶有State的Widget怎么樣?我們來(lái)做一個(gè)帶State的Widget,讓它有生命周期和更新UI的能力。
本次主要內(nèi)容
- 宏觀看state是什么,微觀看State
- State類繼承關(guān)系圖
- 手寫一個(gè)帶State的Widget
宏觀看state是什么,微觀看State
從宏觀來(lái)看,flutter的UI是聲明式的,那為什么是聲明式?這就要從Win32到Web再到Android和Ios說(shuō)起,他們都是命令式的編程風(fēng)格,如下:
//android
TextView tv = TextView()
tv.setText("text")
當(dāng)UI發(fā)生變化的時(shí)候,你必須調(diào)用setText來(lái)實(shí)現(xiàn),但flutter恰相反,它為了減輕開發(fā)人員的負(fù)擔(dān),讓開發(fā)人員只關(guān)心當(dāng)前應(yīng)用的狀態(tài),并交給框架自動(dòng)將狀態(tài)通過(guò)函數(shù)渲染在UI上,那么這樣做有什么好處呢?
- 開發(fā)人員只關(guān)心狀態(tài)的變化,從架構(gòu)上做到了UI和數(shù)據(jù)的分離
- 更深入的講,其實(shí)flutter 真實(shí)的UI對(duì)象是RenderObjects,Widget是不變的,每次刷新UI都會(huì)構(gòu)建新的子Widget樹,并通過(guò)Element過(guò)濾,最終RenderObject只是很小的改動(dòng),提高了渲染的效率。
那么有什么缺點(diǎn)嗎?
- 不合理的狀態(tài)管理,導(dǎo)致整個(gè)頁(yè)面的頻繁build
- 在Widget樹中加入了狀態(tài)的計(jì)算,會(huì)導(dǎo)致狀態(tài)管理的混亂,不統(tǒng)一
最理想的就是如上圖的公式:UI= f(state) 舉個(gè)例子:
class TestState extends StatefulWidget {
@override
_TestState createState() => _TestState();
}
class _TestState extends State<TestState> {
FunState _funState = FunState();
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text(_funState.state + "state"), /// 不推薦
Text(_funState.getState()) /// 推薦寫法,f(state)
],
),
);
}
}
class FunState {
String state = "state";
getState() {
return state + "Test";
}
}
看到了吧,我們不推薦你這樣哦
Text(_funState.state + "state"), /// 不推薦
這就是我要說(shuō)的宏觀state,我們簡(jiǎn)單做個(gè)定義:state其實(shí)就是反應(yīng)出當(dāng)前UI的狀態(tài)。那么微觀呢?其實(shí)就是StatefulWidget的State,都知道每個(gè)StatefulWidget會(huì)對(duì)應(yīng)一個(gè)State,上期我們也學(xué)習(xí)了Widget,了解到Widget實(shí)際上是通過(guò)Element來(lái)展示UI的,那么State到底是什么角色,有什么作用呢?或者說(shuō),為什么google要這么設(shè)計(jì)呢?讓我們來(lái)慢慢揭曉答案,并最終總結(jié)一下。
State類繼承關(guān)系圖
像我常用的Form,F(xiàn)ormField,Overlay,Scaffold Widget,它們都會(huì)對(duì)應(yīng)一個(gè)自己的State,當(dāng)然也有更深一層的繼承關(guān)系如AnimatedWidgetBaseState,但它的子類都是私有的。以及其他的State,通過(guò)類的繼承關(guān)系,大致了解到,State類不需要特別深入的繼承關(guān)系,比Widget和Element都稍微簡(jiǎn)單一些,flutter在設(shè)計(jì)之初,就一致貫穿一個(gè)設(shè)計(jì)思想就是組合大于繼承,所以這也是整個(gè)UI框架的特點(diǎn),也是類圖都很簡(jiǎn)單的主要原因。
手寫一個(gè)帶State的Widget
我們還是上期的套路,繼承最底層的Widget來(lái)實(shí)現(xiàn),這次加一個(gè)State,來(lái)偽裝成StatefulWidget,來(lái)吧。
class StateWidget extends Widget{
/// 構(gòu)造函數(shù)
const StateWidget({ Key key }) : super(key: key);
@override
Element createElement() {
// TODO: implement createElement
throw UnimplementedError();
}
}
創(chuàng)建StateWidget類,繼承自Widget,它讓我們實(shí)現(xiàn)一個(gè)Element,那我們就再創(chuàng)建一個(gè)Element,這次我們用ComponentElement,上期我們使用過(guò)Element了,實(shí)現(xiàn)起來(lái)較麻煩,這期我們要了解的是State對(duì)吧,所以我們繼承ComponentElement來(lái)快速的實(shí)現(xiàn)并理解State
class StateWidget extends Widget{
/// 構(gòu)造函數(shù)
const StateWidget({ Key key }) : super(key: key);
@override
Element createElement() {
return StateElement(this);
}
}
class StateElement extends ComponentElement{
StateElement(Widget widget) : super(widget);
@override
Widget build() {
}
}
我們印象中State有很多屬性和函數(shù),都有那些呢?請(qǐng)看圖
那我們就模仿一下,把這些函數(shù)定義一下,代碼如下
abstract class States<T extends StateWidget> {
T get widget => _widget;
T _widget;
BuildContext get context => _element;
StateElement _element;
@protected
@mustCallSuper
void initState() {}
@protected
@mustCallSuper
void didUpdateWidget(covariant T oldWidget) {}
@protected
@mustCallSuper
void reassemble() {}
@protected
@mustCallSuper
void setState(VoidCallback fn) {}
@protected
@mustCallSuper
void deactivate() {}
@protected
@mustCallSuper
void dispose() {}
@protected
Widget build(BuildContext context);
@protected
@mustCallSuper
void didChangeDependencies() { }
}
好了我們的States就這樣被定義完了,protected關(guān)鍵字跟java的作用域應(yīng)該是一樣的,mustCallSuper是讓子類實(shí)現(xiàn)必須調(diào)用super.當(dāng)前函數(shù),當(dāng)然它還有debugFillProperties等debug相關(guān)的函數(shù),這些我們先不關(guān)心,我們先把最核心的問(wèn)題搞定,接下來(lái),如何將States接入Widget呢?想想我們之前怎么用的?
@override
_TestState createState() => _TestState();
對(duì)的,Widget有個(gè)createState函數(shù),我們也來(lái)加一下,如下:
abstract class StateWidget extends Widget {
/// 構(gòu)造函數(shù)
const StateWidget({Key key}) : super(key: key);
@override
Element createElement() {
return StateElement(this);
}
@protected
@factory
States createState();
}
factory的注釋含義:
用于注釋實(shí)例或靜態(tài)方法。 表示該方法要么是抽象的,要么必須返回新分配的對(duì)象或“null”。另外,每個(gè)實(shí)現(xiàn)或覆蓋該方法都是隱式的使用相同的注釋進(jìn)行注釋。
通過(guò)實(shí)現(xiàn),我們發(fā)現(xiàn)State其實(shí)同時(shí)有Widget和Element的引用的,Widget已經(jīng)完成了,那再來(lái)看看Element如何做呢?我們?cè)賮?lái)看下Element的代碼
class StateElement extends ComponentElement {
///這里將之前的Widget改為StateWidget,免得強(qiáng)轉(zhuǎn)
StateElement(StateWidget widget) : super(widget);
@override
Widget build() {
}
}
繼承自ComponentElement,覆蓋build函數(shù),而State里面恰巧有個(gè)抽象函數(shù)build,那么肯定是這里了
class StateElement extends ComponentElement {
States<StateWidget> get state => _state;
States<StateWidget> _state;
StateElement(StateWidget widget) : super(widget);
@override
Widget build() {
return _state.build(this);
}
}
在Element里緩存一下State,并在build中調(diào)用_state.build(this), 這個(gè)this就是我們熟悉的BuildContext,而BuildContext的實(shí)例就是當(dāng)前Element對(duì)象?,F(xiàn)在你會(huì)發(fā)現(xiàn),_state并沒(méi)有賦值對(duì)吧,它是widget里的createState函數(shù)返回的,那我們什么時(shí)候調(diào)用合適呢?為了避免它多次createState,在構(gòu)造函數(shù)里是不是更合適呢?放進(jìn)去如下
class StateElement extends ComponentElement {
States<StateWidget> get state => _state;
States<StateWidget> _state;
StateElement(StateWidget widget)
///創(chuàng)建State
: _state = widget.createState(),
super(widget){
///斷言判斷
assert(_state._element == null);
///給State里的element賦值,也就是你在State里獲取的context
_state._element = this;
assert(_state._widget == null);
///state里的widget賦值
_state._widget = widget;
}
@override
Widget build() {
return _state.build(this);
}
}
在構(gòu)造函數(shù)里已經(jīng)將State里的element,widget統(tǒng)統(tǒng)賦值了,緊接著就是State的initState()函數(shù),這是我們經(jīng)常用的初始化函數(shù),那么它是在Element什么時(shí)候被調(diào)用的呢?或者說(shuō)什么時(shí)候調(diào)用比較合適,首先一點(diǎn),它肯定只調(diào)用一次,不可能初始化兩次把,這樣不太合理,有人說(shuō)放構(gòu)造里行嗎?我們?cè)賮?lái)看看Element的生命周期
系統(tǒng)調(diào)用createElement后,當(dāng)Element真正被掛載到樹中的時(shí)候,才會(huì)調(diào)用mount,如果Element根本沒(méi)有掛載到UI上,我們是不是就沒(méi)必要初始化呢?那放在構(gòu)造合適嗎?不合適對(duì)吧,所以實(shí)現(xiàn)如下:
class StateElement extends ComponentElement {
States<StateWidget> get state => _state;
States<StateWidget> _state;
bool isNeedInit;
StateElement(StateWidget widget)
///創(chuàng)建State
: _state = widget.createState(),
super(widget){
///斷言判斷
assert(_state._element == null);
///給State里的element賦值,也就是你在State里獲取的context
_state._element = this;
assert(_state._widget == null);
///state里的widget賦值
_state._widget = widget;
}
@override
void mount(Element parent, newSlot) {
super.mount(parent, newSlot);
assert(isNeedInit == null);
_state.initState();
isNeedInit = false;
}
@override
Widget build() {
return _state.build(this);
}
}
在mount函數(shù)中調(diào)用initState函數(shù),并通過(guò)isNeedInit變量控制只調(diào)用一次。緊接著看didChangeDependencies函數(shù),為什么說(shuō)它呢?當(dāng)此State對(duì)象的依賴項(xiàng)更改時(shí)調(diào)用,還有就是在initState后調(diào)用,官方解釋:子類很少重寫此方法,因?yàn)榭蚣芸偸窃谝蕾図?xiàng)更改后調(diào)用build。一些子類確實(shí)重寫了此方法,因?yàn)楫?dāng)它們的依存關(guān)系發(fā)生變化時(shí),它們需要做一些昂貴的工作(例如,網(wǎng)絡(luò)獲取),并且對(duì)于每個(gè)構(gòu)建而言,所做的工作都太昂貴了。
詳細(xì)理解請(qǐng)看大佬分析:http://www.itdecent.cn/p/9cb6c57b796c
其實(shí)說(shuō)白了就是Widget類型發(fā)生變化時(shí),就會(huì)觸發(fā),State里的didChangeDependencies觸發(fā)需要滿足兩個(gè)條件,一個(gè)就是第一次加載,它認(rèn)為Widget類型從null轉(zhuǎn)換為具體的Widget,再一個(gè)就是Element的Widget確實(shí)有了變化,系統(tǒng)調(diào)用Element的didChangeDependencies,這個(gè)時(shí)候才有必要執(zhí)行State的didChangeDependencies,代碼實(shí)現(xiàn)如下:
class StateElement extends ComponentElement {
States<StateWidget> get state => _state;
States<StateWidget> _state;
bool isNeedInit;
bool _didChangeDependencies = false;
StateElement(StateWidget widget)
///創(chuàng)建State
: _state = widget.createState(),
super(widget){
///斷言判斷
assert(_state._element == null);
///給State里的element賦值,也就是你在State里獲取的context
_state._element = this;
assert(_state._widget == null);
///state里的widget賦值
_state._widget = widget;
}
@override
void mount(Element parent, newSlot) {
super.mount(parent, newSlot);
assert(isNeedInit == null);
_state.initState();
///第一次加載的時(shí)候,Widget從Null變?yōu)榫唧w的Widget
_state.didChangeDependencies();
isNeedInit = false;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
///Element的Widget確實(shí)有了變化
_didChangeDependencies = true;
}
@override
void performRebuild() {
///這個(gè)時(shí)候才有必要執(zhí)行State的didChangeDependencies
if (_didChangeDependencies) {
_state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
@override
Widget build() {
return _state.build(this);
}
}
在element的performRebuild函數(shù)中執(zhí)行State的didChangeDependencies,條件就是在系統(tǒng)執(zhí)行了Element的didChangeDependencies函數(shù)。
再往下,我們看下reassemble函數(shù),這個(gè)函數(shù)是干嘛的呢?此回調(diào)是專門為了開發(fā)調(diào)試而提供的,在熱重載(hot reload)時(shí)會(huì)被調(diào)用,此回調(diào)在Release模式下永遠(yuǎn)不會(huì)被調(diào)用。這個(gè)我們不必關(guān)心太多,直接在Element里調(diào)用即可,實(shí)現(xiàn)如下:
@override
void reassemble() {
_state.reassemble();
super.reassemble();
}
再來(lái)看下didUpdateWidget函數(shù),在widget重新構(gòu)建時(shí),F(xiàn)lutter framework會(huì)調(diào)用Widget.canUpdate來(lái)檢測(cè)Widget樹中同一位置的新舊節(jié)點(diǎn),然后決定是否需要更新,如果Widget.canUpdate返回true則會(huì)調(diào)用此回調(diào)。正如之前所述,Widget.canUpdate會(huì)在新舊widget的key和runtimeType同時(shí)相等時(shí)會(huì)返回true,也就是說(shuō)在在新舊widget的key和runtimeType同時(shí)相等時(shí)didUpdateWidget()就會(huì)被調(diào)用,但你發(fā)現(xiàn)Element里面沒(méi)有這個(gè)函數(shù),它只有update函數(shù)。然后我看了下StatefulElement的實(shí)現(xiàn)就是在這里調(diào)用的didUpdateWidget,那我們來(lái)實(shí)現(xiàn)下,看看都什么邏輯
class StateElement extends ComponentElement {
States<StateWidget> get state => _state;
States<StateWidget> _state;
bool isNeedInit;
bool _didChangeDependencies = false;
StateElement(StateWidget widget)
///創(chuàng)建State
: _state = widget.createState(),
super(widget) {
///斷言判斷
assert(_state._element == null);
///給State里的element賦值,也就是你在State里獲取的context
_state._element = this;
assert(_state._widget == null);
///state里的widget賦值
_state._widget = widget;
}
@override
void mount(Element parent, newSlot) {
super.mount(parent, newSlot);
assert(isNeedInit == null);
_state.initState();
///第一次加載的時(shí)候,Widget從Null變?yōu)榫唧w的Widget
_state.didChangeDependencies();
isNeedInit = false;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
///Element的Widget確實(shí)有了變化
_didChangeDependencies = true;
}
@override
void performRebuild() {
///這個(gè)時(shí)候才有必要執(zhí)行State的didChangeDependencies
if (_didChangeDependencies) {
_state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
@override
void reassemble() {
_state.reassemble();
super.reassemble();
}
@override
void update(Widget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
/// 先拿到舊的widget
final StateWidget oldWidget = _state._widget;
/// 強(qiáng)制將狀態(tài)至為可更新
markNeedsBuild();
/// 將State的Widget更新為新的Widget
_state._widget = widget as StateWidget;
/// 回調(diào)_state.didUpdateWidget
_state.didUpdateWidget(oldWidget);
/// 調(diào)用rebuild函數(shù),最終調(diào)用performRebuild,更新Element
rebuild();
}
@override
Widget build() {
return _state.build(this);
}
}
update函數(shù)如上實(shí)現(xiàn),你會(huì)發(fā)現(xiàn)ditUpdateWidget參數(shù)是舊的Widget,這點(diǎn)使用的時(shí)候要注意哦。update函數(shù)結(jié)束了,來(lái)看下deactivate,當(dāng)State對(duì)象從樹中被移除時(shí),會(huì)調(diào)用此回調(diào)。在一些場(chǎng)景下,F(xiàn)lutter framework會(huì)將State對(duì)象重新插到樹中,如包含此State對(duì)象的子樹在樹的一個(gè)位置移動(dòng)到另一個(gè)位置時(shí)(可以通過(guò)GlobalKey來(lái)實(shí)現(xiàn))。如果移除后沒(méi)有重新插入到樹中則緊接著會(huì)調(diào)用dispose()方法??磳?shí)現(xiàn)很簡(jiǎn)單:
@override
void deactivate() {
/// 不需要特殊處理
_state.deactivate();
super.deactivate();
}
再來(lái)看下dispose函數(shù),dispose意義上是釋放,那Element是什么時(shí)候釋放的呢?那肯定是unmount函數(shù)了,所以不言而喻,實(shí)現(xiàn)如下:
@override
void unmount() {
super.unmount();
_state.dispose();
/// 至null來(lái)釋放調(diào)引用
_state._element = null;
_state = null;
}
壓軸的函數(shù)setState來(lái)了,幾乎State所有的生命周期函數(shù)里,沒(méi)有幾個(gè)是有實(shí)現(xiàn)的,而setState需要實(shí)現(xiàn),它是Widget能夠重建的核心,直接上代碼分析哈:
abstract class States<T extends StateWidget> {
T get widget => _widget;
T _widget;
BuildContext get context => _element;
StateElement _element;
@protected
@mustCallSuper
void initState() {}
@protected
@mustCallSuper
void didUpdateWidget(covariant T oldWidget) {}
@protected
@mustCallSuper
void reassemble() {}
@protected
@mustCallSuper
void setState(VoidCallback fn) {
assert(fn != null);
///...省略了狀態(tài)判斷
final dynamic result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() callback argument returned a Future.'),
ErrorDescription(
'The setState() method on $this was called with a closure or method that '
'returned a Future. Maybe it is marked as "async".'
),
ErrorHint(
'Instead of performing asynchronous work inside a call to setState(), first '
'execute the work (without updating the widget state), and then synchronously '
'update the state inside a call to setState().'
),
]);
}
// We ignore other types of return values so that you can do things like:
// setState(() => x = 3);
return true;
}());
/// 最重要的一句,markNeedsBuild,讓Element處與可更新狀態(tài),等下framework層主動(dòng)刷新。
_element.markNeedsBuild();
}
@protected
@mustCallSuper
void deactivate() {}
@protected
@mustCallSuper
void dispose() {}
@protected
Widget build(BuildContext context);
@protected
@mustCallSuper
void didChangeDependencies() {}
}
它首先判斷了fn是否為空,然后加入了state狀態(tài)的判斷,這里我省略了,想看的可以直接看State源碼哦。往下就是對(duì)fn函數(shù)的Future情況處理,最最后調(diào)用了_element.markNeedsBuild();對(duì)哦,這其實(shí)才是我們Widget重新構(gòu)建的關(guān)鍵,它其實(shí)就是改了一個(gè)標(biāo)志位_dirty,設(shè)置true后,framework層就知道它要更新,會(huì)執(zhí)行響應(yīng)的更新,由于實(shí)際的更新是異步的,所以你可以在setState函數(shù)的前后或者函數(shù)中,都可以更新狀態(tài)。
好了終于實(shí)現(xiàn)完了,是騾子是馬,總要拉出來(lái)溜溜,我們自己實(shí)現(xiàn)的States能用嗎?來(lái)實(shí)驗(yàn)一發(fā)。代碼如下:
class TestStateWidget extends StateWidget {
@override
States<StateWidget> createState() {
return TestStates();
}
}
class TestStates extends States<TestStateWidget> {
String data;
int num = 0;
@override
void initState() {
data = "123";
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8),
child: Column(
children: [
Text(data),
MaterialButton(
onPressed: () {
setState(() {
data = "456${num++}";
});
},
child: Text("更新"),
)
],
),
);
}
}
放入main.dart中:
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Column(
children: [
// Center(
// // Center is a layout widget. It takes a single child and positions it
// // in the middle of the parent.
// child: isShow ? const TestWidget() : Container(),
// ),
// MaterialButton(
// onPressed: () {
// showialog(context);
// },
// child: Text('showDialog'),
// ),
// Center(
// // Center is a layout widget. It takes a single child and positions it
// // in the middle of the parent.
// child: isShow
// ? TestWidget(
// key: key,
// )
// : Container(
// padding: EdgeInsets.all(9),
// child: TestWidget(
// key: key,
// ),
// ),
// ),
TestStateWidget()
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
isShow = !isShow;
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
運(yùn)行效果:
點(diǎn)擊更新
再點(diǎn)擊
沒(méi)毛病把,完成了,效果很好,實(shí)現(xiàn)了一套帶State的Widget對(duì)吧。下面我們來(lái)個(gè)總結(jié)。
總結(jié)
生命周期總結(jié)一張圖
- State的狀態(tài)都是來(lái)源于Element,說(shuō)白了就是Element的中介,或者叫委托更合適
- Context可以是Element,但State不能,因?yàn)樗麄儾皇抢^承關(guān)系。
- 掌握Element的重要性又出來(lái)了,因?yàn)镾tate的功能受限于Element。
- 在MVVM架構(gòu)中,State是不是充當(dāng)著VM的角色?我覺(jué)得可以這么認(rèn)為。
等等把,好了,講到這里該告一段落了,期待你的認(rèn)可,點(diǎn)個(gè)贊就行。感謝。