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

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

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

一、什么是 scoped_model

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

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

它直接來(lái)自于 Google 正在開(kāi)發(fā)的新系統(tǒng) Fuchsia 核心 Widgets 中對(duì) Model 類(lèi)的簡(jiǎn)單提取,作為獨(dú)立使用的獨(dú)立 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 的使用非常簡(jiǎn)單,只需要以下三步:

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

三、實(shí)現(xiàn)原理

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

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

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

我們?cè)購(gòu)?Model 入手,可以看出 Model 是一個(gè) 繼承自 Listenable 的抽象類(lèi),主要有一個(gè) _listeners 變量用 Set 來(lái)進(jìn)行存儲(chǔ),復(fù)寫(xiě)了 addListener、removeListener、notifyListeners 方法。在這里不知道大家有沒(méi)有想過(guò) Model 為什么要繼承 Listenable? 在這里先賣(mài)個(gè)關(guān)子,在后面會(huì)詳細(xì)講解。

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

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

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

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

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

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

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

  • 隱式調(diào)用 ScopedModel.of<T>(context) 來(lái)獲取 model。
  • 明確語(yǔ)義化,不然我們每次都需要用 Builder 來(lái)進(jìn)行構(gòu)建,不然將獲取不到 model,還會(huì)拋出異常。

// 不使用 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)然我們還可以使用下面的方法,把它單獨(dú)提取出一個(gè) Widget,代碼如下:

// 單獨(dú)提取 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();
  }
}

是不是很意外?主要起作用的是下面這一段代碼:

單獨(dú)提取出來(lái) Widget,可以獲取到正確的 context,從而可以獲取到離他最近的父 Inherited widgets 實(shí)例。

四、結(jié)束

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

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

相關(guān)文章

最新評(píng)論