Flutter ScopedModel源码浅析

上一篇文章简单了解了一下 ScopedModel 的用法,markdown

这一篇文章咱们来深刻 ScopedModel 源码来看一下它的原理。并发

其实ScopedModel 只有一个文件,咱们直接打开 scoped_model.dart 文件,less

从上往下看。ide

Model

首先在最上面的是 Model ,看一下源码:ui

abstract class Model extends Listenable {
  final Set<VoidCallback> _listeners = Set<VoidCallback>();
  int _version = 0;
  int _microtaskVersion = 0;

  /// [listener] will be invoked when the model changes.
  @override
  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }

  /// [listener] will no longer be invoked when the model changes.
  @override
  void removeListener(VoidCallback listener) {
    _listeners.remove(listener);
  }

  /// Returns the number of listeners listening to this model.
  int get listenerCount => _listeners.length;

  /// Should be called only by [Model] when the model has changed.
  @protected
  void notifyListeners() {
    // We schedule a microtask to debounce multiple changes that can occur
    // all at once.
    if (_microtaskVersion == _version) {
      _microtaskVersion++;
      scheduleMicrotask(() {
        _version++;
        _microtaskVersion = _version;

        // Convert the Set to a List before executing each listener. This
        // prevents errors that can arise if a listener removes itself during
        // invocation!
        _listeners.toList().forEach((VoidCallback listener) => listener());
      });
    }
  }
}
复制代码

Model 继承了 Listenable,至于为何,看到后面就了解了。this

前两个方法都好理解,增长/删除一个监听。spa

后面的 notifyListeners()方法,设计

最主要的逻辑就是把_version++,并消除并发 change 时所可能引起的问题。code

逻辑就是首先判断 _microtaskVersion 和 version 是否相等。cdn

若是相等,则把微任务(_microtaskVersion) ++,而后启动一个微任务来把version++,并处理 listener()。

若是不相等就不作任务,这样就消除了可能由于并发所引起的问题。

ModelFinder

该类是用来查找 Model 的,可是已经弃用了,改用 ScopedModel.of

代码以下:

/// Finds a [Model]. Deprecated: Use [ScopedModel.of] instead.
@deprecated
class ModelFinder<T extends Model> {
  /// Returns the [Model] of type [T] of the closest ancestor [ScopedModel].
  ///
  /// [Widget]s who call [of] with a [rebuildOnChange] of true will be rebuilt
  /// whenever there's a change to the returned model.
  T of(BuildContext context, {bool rebuildOnChange: false}) {
    return ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange);
  }
}
复制代码

ScopedModel

该类为咱们所查看的重点,毕竟库的名字就是它。

二话不说,先来看一下类上面的注释:

/// Provides a [Model] to all descendants of this Widget.
///
/// Descendant Widgets can access the model by using the
/// [ScopedModelDescendant] Widget, which rebuilds each time the model changes,
/// or directly via the [ScopedModel.of] static method.
///
/// To provide a Model to all screens, place the [ScopedModel] Widget above the
/// [WidgetsApp] or [MaterialApp] in the Widget tree.


/// 向此小部件的全部后代提供[Model]。
/// 
/// 子代小部件能够使用[ScopedModelDescendant]小部件访问Model,该小部件在每次模型更改时重建,或者直接经过[ScopedModel.of]静态方法进行访问。
/// 
/// 要向全部页面提供Model,请将[ScopedModel]小部件放在小部件树中的[WidgetsApp]或[MaterialApp]上方。
复制代码

注释上面写的很明显了,若是你要向全部的页面都提供Model,那么就放在树的最上方。

再看一下代码:

class ScopedModel<T extends Model> extends StatelessWidget {
  /// The [Model] to provide to [child] and its descendants.
  final T model;

  /// The [Widget] the [model] will be available to.
  final Widget child;

  ScopedModel({@required this.model, @required this.child})
      : assert(model != null),
        assert(child != null);

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: model,
      builder: (context, _) => _InheritedModel<T>(model: model, child: child),
    );
  }

  /// Finds a [Model] provided by a [ScopedModel] Widget.
  ///
  /// Generally, you'll use a [ScopedModelDescendant] to access a model in the
  /// Widget tree and rebuild when the model changes. However, if you would to
  /// access the model directly, you can use this function instead!
  static T of<T extends Model>(
    BuildContext context, {
    bool rebuildOnChange = false,
  }) {
    final Type type = _type<_InheritedModel<T>>();

    Widget widget = rebuildOnChange
        ? context.inheritFromWidgetOfExactType(type)
        : context.ancestorWidgetOfExactType(type);

    if (widget == null) {
      throw new ScopedModelError();
    } else {
      return (widget as _InheritedModel<T>).model;
    }
  }

  static Type _type<T>() => T;
}
复制代码

首先,ScopedModel 须要一个泛型来确认 Model 的类型,而且是一个无状态的 widget。

而后 ScopedModel 须要一个child Widget,这个 child Widget 就能够说是咱们的 MaterialApp了。

接着是 build()方法,这就是 ScopedModel 最精髓的地方。

build() 方法

build()方法使用了 AnimatedBuilder来构建 widget,由于 AnimatedBuilder 会自动监听 animation 数据的更改。

AnimatedBuilder的参数 animation 须要的是一个 Listenable 对象,而咱们的 Model 正好继承的就是Listenable。

因此咱们在自定义的 Model 中,须要更新的地方手动调用 notifyListeners()

notifyListeners()前面也说了,就是把 _version++。

既然_version++了,那就达到了咱们想要的目的,更新使用了该数据的UI。

of() 方法

of() 方法咱们很熟悉,是用来获取 Model 的。

用起来很简单,可是里面的内容却很多,看一下源码:

static T of<T extends Model>(
  BuildContext context, {
    bool rebuildOnChange = false,
  }) {
  final Type type = _type<_InheritedModel<T>>();

  Widget widget = rebuildOnChange
    ? context.inheritFromWidgetOfExactType(type)
    : context.ancestorWidgetOfExactType(type);

  if (widget == null) {
    throw new ScopedModelError();
  } else {
    return (widget as _InheritedModel<T>).model;
  }
}

static Type _type<T>() => T;
复制代码

of() 方法须要传入一个 Model 的泛型,

首先获取了一下它的 Type,随后根据 rebuildOnChange 的值用 inheritFromWidgetOfExactType/ ancestorWidgetOfExactType来查找widget。

这里解释一下这两个方法:

inheritFromWidgetOfExactType 是用来获取给定类型的最近的 widget,而且在值更新的时候自动从新构建。

ancestorWidgetOfExactType 是用来获取给定类型最近的 祖先 Widget,而且在值更新的时候不从新构建。

因此这样就控制住了没有必要的UI更新。

_InheritedModel

在上面的build 和 of 方法中,都出现了该 Model,

他其实就是一个 InheritedWidget,重写了 updateShouldNotify方法来控制重绘。

代码以下:

class _InheritedModel<T extends Model> extends InheritedWidget {
  final T model;
  final int version;

  _InheritedModel({Key key, Widget child, T model})
      : this.model = model,
        this.version = model._version,
        super(key: key, child: child);

  @override
  bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
      (oldWidget.version != version);
}
复制代码

重绘的逻辑就是 version 是否相同。

ScopedModelDescendant

该类就是一个包装了 ScopedModel.of方法的无状态 widget。

代码以下:

class ScopedModelDescendant<T extends Model> extends StatelessWidget {
  /// Called whenever the [Model] changes.
  final ScopedModelDescendantBuilder<T> builder;

  /// An optional constant child that does not depend on the model. This will
  /// be passed as the child of [builder].
  final Widget child;

  /// An optional constant that determines whether the
  final bool rebuildOnChange;

  /// Constructor.
  ScopedModelDescendant({
    @required this.builder,
    this.child,
    this.rebuildOnChange = true,
  });

  @override
  Widget build(BuildContext context) {
    return builder(
      context,
      child,
      ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
    );
  }
}
复制代码

具体就不细说了,由于几乎没有逻辑在里面。

总结

能够看到ScopedModel 的设计真的是很是巧妙,

利用 AnimatedBuilder 和 InheritWidget 来作到全局的状态管理。

关于微任务(Microtask) 和 EventQueue的理解,我会继续出一篇文章来讲一下。

那多个Model如何使用?利用 mixin 就能够了。

相关文章
相关标签/搜索