欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Flutter 狀態(tài)管理scoped model源碼解讀

 更新時間:2022年11月11日 11:23:21   作者:feelingHy  
這篇文章主要為大家介紹了Flutter 狀態(tài)管理scoped model源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一、什么是 scoped_model

本文主要從 scoped_model 的簡單使用說起,然后再深入源碼進(jìn)行剖析(InheritedWidget、Listenable、AnimatedBuilder),不會探討 Flutter 狀態(tài)管理的優(yōu)劣,單純?yōu)榱藢W(xué)習(xí)作者的設(shè)計思想。

scoped_model 是一個第三方 Dart 庫,可以讓您輕松的將數(shù)據(jù)模型從父 Widget 傳遞到子 Widget。此外,它還會在模型更新時重新構(gòu)建所有使用該模型的子 Widget。

它直接來自于 Google 正在開發(fā)的新系統(tǒng) Fuchsia 核心 Widgets 中對 Model 類的簡單提取,作為獨立使用的獨立 Flutter 插件發(fā)布。

二、用法

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 的實現(xiàn),如 CounterModel,這里需要注意的是 CounterModel 一定要繼承自 Model(為什么一定要繼承 Model 我們后面細(xì)說)并且在狀態(tài)改變的時候執(zhí)行 notifyListeners。
  • 使用 ScopedModel Widget 包裹需要用到 Model 的 Widget。
  • 使用 ScopedModelDescendant 或者 ScopedModel.of<CounterModel>(context) 來進(jìn)行獲取數(shù)據(jù)。

三、實現(xiàn)原理

在 scoped_model 中的整個實現(xiàn)中,它很巧妙的借助了 AnimatedBuilder、Listenable、InheritedWidget 等 Flutter 的基礎(chǔ)特性。

scoped_model 使用了觀察者模式,將數(shù)據(jù)放在父 Widget,子 Widget 通過找到父 Widget 的 model 進(jìn)行數(shù)據(jù)渲染,最后改變數(shù)據(jù)的時候再將數(shù)據(jù)傳回,父 Widget 再通知所有用到了該 model 的子 Widget 去更新狀態(tài)。

我們首先從 ScopedModel 入手,通過源碼我們不難發(fā)現(xiàn),ScopedModel 是一個 StatelessWidget 最后返回一個 AnimatedBuilder,在 AnimatedBuilder 中在通過 builder 返回 _InheritedModel。

我們再從 Model 入手,可以看出 Model 是一個 繼承自 Listenable 的抽象類,主要有一個 _listeners 變量用 Set 來進(jìn)行存儲,復(fù)寫了 addListener、removeListenernotifyListeners 方法。在這里不知道大家有沒有想過 Model 為什么要繼承 Listenable? 在這里先賣個關(guān)子,在后面會詳細(xì)講解。

如果只是單單看 ScopedModelModel 好像也看不出來什么巧妙之處,但是如果把 ScopedModel 中返回的 AnimatedBuilderModel 所繼承的 Listenable 結(jié)合起來進(jìn)行思考就會發(fā)現(xiàn),AnimatedBuilder 繼承自 AnimatedWidget,在 AnimatedWidget 的生命周期中會對 Listenable 添加監(jiān)聽,而 Model 正好就實現(xiàn)了 Listenable 接口。

Model 實現(xiàn)了 Listenable 接口,內(nèi)部剛好有一個 Set<VoidCallback> _listeners 用來保存接收者。當(dāng) Model 賦值給 AnimatedBuilder 中的 animation 時,Listenable 的 addListener 就會被調(diào)用,然后添加一個 _handleChange 方法,_handleChange 內(nèi)部只有一行代碼 setState((){}),當(dāng)調(diào)用 notifyListeners 時,會從創(chuàng)建一個 Microtask,去執(zhí)行一遍 _listeners 中的 _handleChange ,當(dāng) _handleChange 被調(diào)用時就會進(jìn)行更新 UI 界面。其實這里也就解釋了 Model 為什么要繼承 Listenable

不知道大家發(fā)現(xiàn)沒有,講了這么多,還沒有講到 ScopedModelDescendant 到底是干什么的?那我就不得不先說起 InheritedWidget 了。

InheritedWidget 是 Flutter 中非常重要的一個功能型組件,它提供了一種在 Widget 樹中從上到下共享數(shù)據(jù)的方式,比如我們在應(yīng)用的根 Widget 中通過 InheritedWidget 共享了一個數(shù)據(jù),那么我們便可以在任意子 Widget 中來獲取該共享的數(shù)據(jù)。它主要有以下兩個作用:

  • 子 Widget 可以通過 Inherited Widgets 提供的靜態(tài) of 方法拿到離他最近的父Inherited widgets 實例。
  • 當(dāng) Inherited Widgets 改變 state 之后,會自動觸發(fā) state 消費者的 rebuild 行為。

scoped_model 中我們可以通過 ScopedModel.of<CountModel>(context) 來獲取 我們的 model,最主要的就是在 ScopedModel 中返回了 AnimatedBuilder,而 AnimatedBuilder 中 builder 又返回了 _InheritedModel, _InheritedModel 又繼承了 InheritedWidget

言歸正傳,我們一起回到 ScopedModelDescendant 的主題,不知道大家有沒有嘗試過,不用 ScopedModelDescendant 來獲取 model 會發(fā)生什么樣的情況?通過窺探源碼我們發(fā)現(xiàn)有 ScopedModelError 這樣一個異常類,說的已經(jīng)很明確了,必須要提供 ScopedModelDescendant。what ?其實它主要做了以下 2 件事情:

  • 隱式調(diào)用 ScopedModel.of<T>(context) 來獲取 model。
  • 明確語義化,不然我們每次都需要用 Builder 來進(jìn)行構(gòu)建,不然將獲取不到 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 的方式,當(dāng)然我們還可以使用下面的方法,把它單獨提取出一個 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 實例。

四、結(jié)束

以上就是我對 scoped_model 的使用以及部分源碼的解讀,如果有不足之處,還請指教。 最后,大家也可以思考一下,我們是如何通過 context 就能獲取到共享的 Model 呢?

以上就是Flutter 狀態(tài)管理scoped model源碼解讀的詳細(xì)內(nèi)容,更多關(guān)于Flutter狀態(tài)管理 scoped model的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論