本文首要从 scoped_model 的简略运用说起,然后再深化源码进行分析(InheritedWidget、Listenable、AnimatedBuilder),不会探讨 Flutter 状况管理的好坏,单纯为了学习作者的规划思维。


一、什么是 scoped_model

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 的运用非常简略,只需求以下三步:

  1. 定义 Model 的完成,如 CounterModel,这里需求注意的是 CounterModel 一定要承继自 Model(为什么一定要承继 Model 咱们后面细说)并且在状况改动的时分履行 notifyListeners。
  2. 运用 ScopedModel Widget 包裹需求用到 Model 的 Widget。
  3. 运用 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

Flutter 状态管理源码解读 - scoped_model

咱们再从 Model 下手,能够看出 Model 是一个 承继自 Listenable抽象类,首要有一个 _listeners 变量用 Set 来进行存储,复写了 addListenerremoveListenernotifyListeners 办法。在这里不知道咱们有没有想过 Model 为什么要承继 Listenable? 在这里先卖个关子,在后面会详细讲解。

Flutter 状态管理源码解读 - scoped_model

假如仅仅单单看 ScopedModelModel 好像也看不出来什么巧妙之处,但是假如把 ScopedModel 中回来的 AnimatedBuilderModel 所承继的 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

Flutter 状态管理源码解读 - scoped_model

Flutter 状态管理源码解读 - scoped_model

不知道咱们发现没有,讲了这么多,还没有讲到 ScopedModelDescendant 到底是干什么的?那我就不得不先说起 InheritedWidget 了。

InheritedWidget 是 Flutter 中非常重要的一个功能型组件,它供给了一种在 Widget 树中从上到下同享数据的办法,比如咱们在运用的根 Widget 中经过 InheritedWidget 同享了一个数据,那么咱们便能够在任意子 Widget 中来获取该同享的数据。它首要有以下两个作用:

  1. 子 Widget 能够经过 Inherited Widgets 供给的静态 of 办法拿到离他最近的父Inherited widgets 实例。
  2. Inherited Widgets 改动 state 之后,会主动触发 state 消费者的 rebuild 行为。

Flutter 状态管理源码解读 - scoped_model

scoped_model 中咱们能够经过 ScopedModel.of<CountModel>(context) 来获取 咱们的 model,最首要的便是在 ScopedModel 中回来了 AnimatedBuilder,而 AnimatedBuilder 中 builder 又回来了 _InheritedModel_InheritedModel 又承继了 InheritedWidget

言归正传,咱们一起回到 ScopedModelDescendant 的主题,不知道咱们有没有尝试过,不用 ScopedModelDescendant 来获取 model 会产生什么样的状况?经过窥视源码咱们发现有 ScopedModelError 这样一个反常类,说的现已很清晰了,必需要供给 ScopedModelDescendant。what ?其实它首要做了以下 2 件工作:

  1. 隐式调用 ScopedModel.of<T>(context) 来获取 model。
  2. 清晰语义化,不然咱们每次都需求用 Builder 来进行构建,不然将获取不到 model,还会抛出反常。

Flutter 状态管理源码解读 - scoped_model

Flutter 状态管理源码解读 - scoped_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();
  }
}

是不是很意外?首要起作用的是下面这一段代码:

Flutter 状态管理源码解读 - scoped_model

独自提取出来 Widget,能够获取到正确的 context,从而能够获取到离他最近的父 Inheritedwidgets 实例。


四、完毕

以上便是我对 scoped_model 的运用以及部分源码的解读,假如有不足之处,还请指导。 最终,咱们也能够考虑一下,咱们是怎么经过 context 就能获取到同享的 Model 呢?