網站首頁 編程語言 正文
一、什么是 scoped_model
本文主要從 scoped_model 的簡單使用說起,然后再深入源碼進行剖析(InheritedWidget、Listenable、AnimatedBuilder),不會探討 Flutter 狀態管理的優劣,單純為了學習作者的設計思想。
scoped_model 是一個第三方 Dart 庫,可以讓您輕松的將數據模型從父 Widget 傳遞到子 Widget。此外,它還會在模型更新時重新構建所有使用該模型的子 Widget。
它直接來自于 Google 正在開發的新系統 Fuchsia 核心 Widgets 中對 Model 類的簡單提取,作為獨立使用的獨立 Flutter 插件發布。
二、用法
class CounterModel extends Model { int _counter = 0; int get counter => _counter; static CounterModel of(BuildContext context) => ScopedModel.of<CounterModel>(context, rebuildOnChange: true); void increment() { _counter++; notifyListeners(); } } class ScopedModelDemoPage extends StatelessWidget { const ScopedModelDemoPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('scopedModel'), centerTitle: true, ), body: ScopedModel( model: CounterModel(), child: ScopedModelDescendant<CounterModel>( builder: (context, child, model) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('${model.counter}'), OutlinedButton( onPressed: ScopedModel.of<CounterModel>(context).increment, // onPressed: model.increment, child: Text('add'), ), ], ), ), ), ), ); } }
從上面的代碼可以看出 scoped_model 的使用非常簡單,只需要以下三步:
- 定義
Model
的實現,如 CounterModel,這里需要注意的是 CounterModel 一定要繼承自Model
(為什么一定要繼承 Model 我們后面細說)并且在狀態改變的時候執行 notifyListeners。 - 使用
ScopedModel
Widget 包裹需要用到Model
的 Widget。 - 使用
ScopedModelDescendant
或者ScopedModel.of<CounterModel>(context)
來進行獲取數據。
三、實現原理
在 scoped_model 中的整個實現中,它很巧妙的借助了 AnimatedBuilder、Listenable、InheritedWidget 等 Flutter 的基礎特性。
scoped_model 使用了觀察者模式,將數據放在父 Widget,子 Widget 通過找到父 Widget 的 model 進行數據渲染,最后改變數據的時候再將數據傳回,父 Widget 再通知所有用到了該 model 的子 Widget 去更新狀態。
我們首先從 ScopedModel
入手,通過源碼我們不難發現,ScopedModel 是一個 StatelessWidget
最后返回一個 AnimatedBuilder
,在 AnimatedBuilder
中在通過 builder 返回 _InheritedModel
。
我們再從 Model
入手,可以看出 Model
是一個 繼承自 Listenable
的抽象類,主要有一個 _listeners 變量用 Set 來進行存儲,復寫了 addListener
、removeListener
、notifyListeners
方法。在這里不知道大家有沒有想過 Model
為什么要繼承 Listenable
? 在這里先賣個關子,在后面會詳細講解。
如果只是單單看 ScopedModel
和 Model
好像也看不出來什么巧妙之處,但是如果把 ScopedModel
中返回的 AnimatedBuilder
和 Model
所繼承的 Listenable
結合起來進行思考就會發現,AnimatedBuilder
繼承自 AnimatedWidget
,在 AnimatedWidget
的生命周期中會對 Listenable
添加監聽,而 Model
正好就實現了 Listenable
接口。
Model
實現了 Listenable
接口,內部剛好有一個 Set<VoidCallback> _listeners
用來保存接收者。當 Model
賦值給 AnimatedBuilder
中的 animation 時,Listenable
的 addListener 就會被調用,然后添加一個 _handleChange
方法,_handleChange
內部只有一行代碼 setState((){})
,當調用 notifyListeners
時,會從創建一個 Microtask,去執行一遍 _listeners
中的 _handleChange
,當 _handleChange
被調用時就會進行更新 UI 界面。其實這里也就解釋了 Model
為什么要繼承 Listenable
。
不知道大家發現沒有,講了這么多,還沒有講到 ScopedModelDescendant
到底是干什么的?那我就不得不先說起 InheritedWidget
了。
InheritedWidget
是 Flutter 中非常重要的一個功能型組件,它提供了一種在 Widget 樹中從上到下共享數據的方式,比如我們在應用的根 Widget 中通過 InheritedWidget
共享了一個數據,那么我們便可以在任意子 Widget 中來獲取該共享的數據。它主要有以下兩個作用:
- 子 Widget 可以通過
Inherited
Widgets 提供的靜態 of 方法拿到離他最近的父Inherited
widgets 實例。 - 當
Inherited
Widgets 改變 state 之后,會自動觸發 state 消費者的 rebuild 行為。
在 scoped_model
中我們可以通過 ScopedModel.of<CountModel>(context)
來獲取 我們的 model,最主要的就是在 ScopedModel
中返回了 AnimatedBuilder
,而 AnimatedBuilder
中 builder 又返回了 _InheritedModel
, _InheritedModel
又繼承了 InheritedWidget
。
言歸正傳,我們一起回到 ScopedModelDescendant
的主題,不知道大家有沒有嘗試過,不用 ScopedModelDescendant
來獲取 model 會發生什么樣的情況?通過窺探源碼我們發現有 ScopedModelError
這樣一個異常類,說的已經很明確了,必須要提供 ScopedModelDescendant
。what ?其實它主要做了以下 2 件事情:
- 隱式調用
ScopedModel.of<T>(context)
來獲取 model。 - 明確語義化,不然我們每次都需要用
Builder
來進行構建,不然將獲取不到 model,還會拋出異常。
// 不使用 ScopedModelDescendant 使用 Builder 的用法 class ScopedModelDemoPage extends StatelessWidget { const ScopedModelDemoPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('scopedModel'), centerTitle: true, ), body: ScopedModel( model: CounterModel(), child: Builder( builder: (context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('${CounterModel.of(context).counter}'), OutlinedButton( onPressed: CounterModel.of(context).increment, child: Text('add1'), ), ], ); }, ), // child: ScopedModelDescendant<CounterModel>( // builder: (context, child, model) => Center( // child: Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Text('${model.counter}'), // OutlinedButton( // onPressed: ScopedModel.of<CounterModel>(context).increment, // // onPressed: model.increment, // child: Text('add'), // ), // ], // ), // ), // ), ), ); } }
除了上面這種使用 Builder 的方式,當然我們還可以使用下面的方法,把它單獨提取出一個 Widget,代碼如下:
// 單獨提取 Widget 的方式 class ScopedModelDemoPage extends StatelessWidget { const ScopedModelDemoPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('scopedModel'), centerTitle: true, ), body: ScopedModel<CounterModel>( model: CounterModel(), // 不使用 ScopedModelDescendant 的用法 // child: Builder( // builder: (context) { // return Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Text('${CounterModel.of(context).counter}'), // OutlinedButton( // onPressed: CounterModel.of(context).increment, // child: Text('add1'), // ), // ], // ); // }, // ), child: NewWidget(), ), ); } } class NewWidget extends StatelessWidget { const NewWidget({ Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('${CounterModel.of(context).counter}'), OutlinedButton( onPressed: CounterModel.of(context).increment, // onPressed: model.increment, child: Text('add'), ), ], ), ); } } class CounterModel extends Model { int _counter = 0; int get counter => _counter; static CounterModel of(BuildContext context) => ScopedModel.of<CounterModel>(context, rebuildOnChange: true); void increment() { _counter++; notifyListeners(); } }
是不是很意外?主要起作用的是下面這一段代碼:
單獨提取出來 Widget,可以獲取到正確的 context,從而可以獲取到離他最近的父 Inherited
?widgets 實例。
四、結束
以上就是我對 scoped_model 的使用以及部分源碼的解讀,如果有不足之處,還請指教。 最后,大家也可以思考一下,我們是如何通過 context
就能獲取到共享的 Model 呢?
原文鏈接:https://juejin.cn/post/7163838946219130894
相關推薦
- 2022-04-17 axios token失效刷新token怎么重新請求_Token 刷新并發處理解決方案
- 2022-08-04 Python中reduce函數詳解_python
- 2022-10-04 在shell腳本中激活conda虛擬環境的方法總結_linux shell
- 2022-12-01 Go初學者踩坑之go?mod?init與自定義包的使用_Golang
- 2022-07-13 Android單選多選按鈕的使用方法_Android
- 2023-03-29 基于WPF實現多選下拉控件的示例代碼_C#教程
- 2022-07-19 react組件通訊的三種方式props:父組件和子組件互相通訊、兄弟組件通訊
- 2022-10-23 Android性能優化全局異常處理詳情_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支