這是我參與8月更文挑戰的第5天,活動詳情檢視:8月更文挑戰
本文翻譯自 Flutter 官方推薦的文章:Managing Flutter Application State With InheritedWidgets。通過官網文件或推薦文章,能夠讓我們更好地瞭解 Flutter 的狀態管理機制。
前言
上篇Flutter入門與實戰(四十一): 從InheritedWidget深入瞭解狀態管理機制(上)介紹了使用自定義InheritedWidget
子類 ModelBinding
實現子元件直接訪問狀態,從而達到無需沿著元件樹層層傳遞的目的,減少了父子元件間的耦合。但是也存在兩個問題:
ModelBinding
類不通用,每個頁面都需要自己定義一個InheritedWidget
子類。- 狀態改變的回撥函式還是需要從頂層傳遞到具體操作狀態的元件,耦合沒有完全解除。
本篇將對上一個 ModelBinding
類進行改造,實現一個更通用,耦合度更低的 ModelBinding
類。
解耦狀態更新回撥
InheritedWidget
方式的版本將元件和 model
進行繫結後簡化了模型的更新。現在,任何 ModelBinding
的下級元件都可以獲取這個模型並更新它,因此也就無需通過回撥來處理了。
如前所述,通過 ViewModel.of(context)
,ModelBinding
的下級可以獲取模型的值,從而使得依賴模型的元件可以自動跟隨模型的變更進行更新。同樣的,ModelBinding
的子元件也可以通過ViewModel.update(context, newModel)
這個方式來更新模型資料。看起來不錯哦!
為了支援使用靜態的 ViewModel.update
方法來更新模型,我們需要引入額外一個額外的有狀態元件。這會稍微有點複雜。
- 將
ModelBinding
變成了一個有狀態元件,並且持有當前狀態模型物件。 - 為
ModelBinding
構建了一個_ModelBindingScope
的InheritedWidget
子元件,該子元件引用了State<ModelBinding>
——即_ModelBingingState
,其實就相當於是引用了父級的狀態。 - 為了改變
ModelBinding
當前模型的值,從而在呼叫setState
方法重建ModelBinding
,並重建下級的_ModelBindingScope
。 - 通過
_ModelBindingScope
來實現ViewModel
類的靜態獲取模型物件及更新物件
程式碼如下所示:
static ViewModel of(BuildContext context) {
_ModelBindingScope scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
return scope.modelBindingState.currentModel;
}
static void update(BuildContext context, ViewModel newModel) {
_ModelBindingScope scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
scope.modelBindingState.updateModel(newModel);
}
複製程式碼
現在,任何 ModelBinding
的子元件都可以使用這些方法來更新模型資料了,下面的按鈕程式碼就同時獲取和更新了模型的資料。
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Hello World ${ViewModel.of(context).value}'),
onPressed: () {
ViewModel model = ViewModel.of(context);
ViewModel.update(context, ViewModel(value: model.value + 1));
},
);
}
複製程式碼
執行一下,一切正常(完整程式碼:model_binding_v1.dart),是不是該慶祝一下?然而,如果我們有技術上百個狀態的話我們要寫幾十上百個 ModelBinding
類,而且每個 Model 都要提供一個靜態的 of(context)
和 update
方法?
使用泛型構建通用的 ModelBinding 類
我們要保證 ModelBinding的通用性,那就需要使用泛型來動態繫結狀態模型物件。首先從最底層改起,先修改_ModelBIndingScope類:
class _ModelBindingScope<T> extends InheritedWidget {
_ModelBindingScope({
Key key,
@required this.modelBindingState,
Widget child,
}) : assert(modelBindingState != null),
super(key: key, child: child);
final _ModelBindingV2State<T> modelBindingState;
@override
bool updateShouldNotify(_ModelBindingScope oldWidget) => true;
}
複製程式碼
改起來很簡單,只需要加上泛型引數就好了。接下來是_ModelBindV2State,這個類也許改成泛型。
class _ModelBindingV2State<T> extends State<ModelBindingV2<T>> {
T currentModel;
@override
void initState() {
super.initState();
currentModel = widget.create();
}
void updateModel(T newModel) {
if (currentModel != newModel) {
setState(() {
currentModel = newModel;
});
}
}
@override
Widget build(BuildContext context) {
return _ModelBindingScope<T>(
modelBindingState: this,
child: widget.child,
);
}
}
複製程式碼
實際上,也只是增加了泛型引數,這裡有個地方需要注意的是,之前我們是在_ModelBidingV2State 中直接構建初始狀態了,現在由於是泛型,我們沒法直接構建泛型物件,因此需要從有狀態元件中獲取。這裡我們在 initState 中呼叫了ModeBinding 類的create 方法返回一個泛型物件(當然,也可以直接使用賦值,取決於 ModelBinding 類如何獲取初始狀態物件)。
接下來是對 ModelBinding 類進行改造,這裡一個是需要傳遞一個獲取泛型物件的方法 create 給建構函式,另外就是將之前放在 ViewModel的獲取模型物件的方法和更新方法提升到 ModelBinding 類來,並且變成泛型方法,從而對外只需要 ModelBinding 類就可以完成狀態模型物件的獲取和更新,最大程度簡化狀態模型物件的實現。
class ModelBindingV2<T> extends StatefulWidget {
ModelBindingV2({Key key, @required this.create, this.child})
: assert(create != null),
super(key: key);
final ValueGetter<T> create;
final Widget child;
@override
_ModelBindingV2State<T> createState() => _ModelBindingV2State<T>();
static T of<T>(BuildContext context) {
_ModelBindingScope<T> scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
return scope.modelBindingState.currentModel;
}
static void update<T>(BuildContext context, T newModel) {
_ModelBindingScope<T> scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
scope.modelBindingState.updateModel(newModel);
}
}
複製程式碼
改造完成之後,我們的實際 Controller 的程式碼就變成下面這樣了:
class StateViewControllerV2 extends StatelessWidget {
StateViewControllerV2({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('模型繫結泛型版'),
),
body: Center(
child: ModelBindingV2(
create: () => ViewModel(),
child: ViewController(),
),
),
);
}
}
class ViewController extends StatelessWidget {
const ViewController({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Hello World ${ModelBindingV2.of<ViewModel>(context).value}'),
onPressed: () {
ViewModel model = ModelBindingV2.of<ViewModel>(context);
ModelBindingV2.update(context, ViewModel(value: model.value + 1));
},
);
}
}
複製程式碼
可以看到,整個 ModelBinding 類完成了狀態的獲取和更新,而且適用於任何狀態模型類。
總結
本篇程式碼已經上傳至:狀態管理示例。從上一篇和本篇來看,狀態管理的核心元件其實是 InheritedWidget。藉助 InheritedWidget 能夠在其狀態發生改變後,將依賴於該元件狀態的全部下級元件進行更新。而通過泛型的使用,我們構建了一個最簡單的通用狀態管理元件。當然,實際應用中的狀態管理遠比這複雜,但是明白了其中的原理,對我們優化效能會有更大的幫助。
我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章。
??:覺得有收穫請點個贊鼓勵一下!
?:收藏文章,方便回看哦!
?:評論交流,互相進步!