Flutter狀態管理(1)——InheritedWidget
- 2019 年 10 月 4 日
- 筆記
狀態管理
Flutter的狀態管理分為兩種:局部狀態和全局狀態。
局部狀態:根據官方的含義,就是一個StatefulWidget可以搞定的,比如BottomNavigationBar、PageView等等,其他Widget不需要知道你的狀態,你也不需要依賴其他Widget的狀態;setState可以實現狀態的切換; 全局狀態:整個app很多頁面都需要用到的狀態,比如是否登錄了,用戶名、用戶id等;這個的實現有很多方式,可以參考List of state management approaches
局部狀態和全局狀態的劃分可以參考下圖:

Flutter狀態管理系列主要指的是全局狀態的管理,主要介紹的幾種實現方式有:
- InheritedWidget
- StreamBuilder
- Provider
前兩種,框架自帶;第三種是google推薦使用的三方庫。 本文將首先介紹InheritedWidget的實現方式。
InheritedWidget實現全局狀態的管理
在Flutter數據傳輸中,介紹了數據從上向下的傳輸方式,其中介紹了InheritedWidget的使用,當時的例子是在一個page裡面,數據從上向下傳輸,不熟悉的朋友可以先去看下那篇文章。
這裡,將使用InheritedWidget作為全局狀態的管理者,那麼將InheritedWidget作為根Widget可以實現下面的Widget都可以獲取到該Widget持有的狀態。
下面的例子,涉及兩個頁面,第一個頁面展示是否登錄資訊,第二個頁面根據是否登錄了來走是登錄、還是退出的邏輯。
實現
Step1:狀態類的定義
class LoginState { bool isLogin = false; @override bool operator ==(other) { return isLogin == (other as LoginState).isLogin; } }
這邊比較簡單,定義了一個LoginState,isLogin表示是否登錄的狀態。
重寫了==,isLogin狀態不變,可以認為LoginState沒有變化。
Step2:InheritedWidget的定義
class LoginStateWidget extends InheritedWidget { const LoginStateWidget({Key key, this.loginState, Widget child}) : super(key: key, child: child); static LoginStateWidget of(BuildContext context) { return context.inheritFromWidgetOfExactType(LoginStateWidget); } final LoginState loginState; @override bool updateShouldNotify(LoginStateWidget oldWidget) { return oldWidget.loginState == this.loginState; } }
InheritedWidget持有LoginState對象;另外需要提供of靜態方法,方法實現是context.inheritedFromWidgetOfExactType;updateShouldNotify方法是用來判斷當該Widget變化時,這裡面如果LoginState變化了,即isLogin參數變化了,才會去通知那些依賴該Widget的Widget變化。
Step3:根Widget使用
void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return LoginStateWidget( child: MaterialApp( home: Scaffold( appBar: AppBar( title: Text('狀態管理'), ), body: MainPage(), ), ), loginState: LoginState(), ); } }
可以看到,在MaterialApp外面套了一層LoginStateWidget,並且傳入了一個初始的LoginState。
Step4:子Widget獲取狀態
獲取的方式是:
LoginStateWidget loginStateWidget=LoginStateWidget.of(context);
拿到了Widget之後,就可以拿到LoginState進行操作了。
主頁面的程式碼如下:
class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { LoginState loginState = LoginStateWidget.of(context).loginState; return Container( child: Center( child: RaisedButton( onPressed: () { Navigator.of(context).push(MaterialPageRoute(builder: (context) { return LoginPage(); })); }, child: Text(loginState.isLogin ? '已登錄' : '未登錄'), ), ), ); } }
這裡Text會根據登錄狀態實行文字切換,但是依然聲明的是一個StatelessWidget。
LoginPage的程式碼如下,和MainPage差距不大:
class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { LoginState loginState = LoginStateWidget.of(context).loginState; return Scaffold( appBar: AppBar( title: Text('登錄頁面'), ), body: Container( child: Center( child: RaisedButton( onPressed: () { loginState.isLogin = !loginState.isLogin; Navigator.of(context).pop(); }, child: Text(loginState.isLogin ? '退出登錄' : '登錄'), ), ), ), ); } }
運行效果

InheritedWidget的問題
這裡我們只有一個狀態,試想,如果有多個全局狀態,那麼應該怎麼實現?
- 一個InheritedWidget,多個表示狀態的類;需要考慮InheritedWidget的updateShouldNotify方法需要如何實現,每個狀態稍有不同就去通知,那麼全局那麼多Widget都會收到消息,而更多的Widget只是關注某一狀態,是否會造成性能損失?
- 多個InheritedWidget,每個InheritedWidget管理一個狀態類,這個時候需要嵌套InheritedWidget,如果每個狀態之間還有依賴的話,還需要考慮InheritedWidget的嵌套順序
上面兩種方案,是我目前能想到的兩種方案,可以看到,都有各種各樣的問題。
原理
關於InheritedWidget的實現原理,可以參考從 Flutter 源碼看 InheritedWidget 內部實現原理
總結
可以發現InheritedWidget的使用,可以看做是在全局創建InheritedWidget及其管理的狀態,然後所有的子Widget都可以獲取到該對象及其狀態,然後每個可以獲取的Widget即是Producer又是Consumer,一切操作就是操作對象一樣更改狀態,剩下的系統會幫你做掉。
參考
- Differentiate between ephemeral state and app state
- List of state management approaches
- 從 Flutter 源碼看 InheritedWidget 內部實現原理