Flutter入門與實戰(四十二): 從InheritedWidget深入瞭解狀態管理機制(下)

島上碼農發表於2021-08-05

這是我參與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 構建了一個_ModelBindingScopeInheritedWidget 子元件,該子元件引用了 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 入門與實戰的專欄文章。

??:覺得有收穫請點個贊鼓勵一下!

?:收藏文章,方便回看哦!

?:評論交流,互相進步!

相關文章