状态管理之scoped_model源码分析

480 阅读6分钟

上一篇中介绍了它的使用,简单分为三步 首先新建继承于Model的类,在其中添加notifyListeners方法,其次在顶层用ScopedModel来包裹,并把刚才创建好的model传递进来,最后在使用的地方调用ScopedModelDescendant或者ScopedModel.of(context)来使用。所以我们就看看这三步中每一步都干了什么。(版本1.0.1)

Model类:源码如下

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());
      });
    }
  }
}

首先这个类继承于Listenable,而这个listenable官方给出的解释就是

/// An object that maintains a list of listeners.
//他维护一个listener的集合
/// The listeners are typically used to notify clients that the object has been updated
///这些集合一般用来通知客户端,当前的对象有了更新
abstract class Listenable {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const Listenable();

  /// Return a [Listenable] that triggers when any of the given [Listenable]s
  /// themselves trigger.
  ///
  /// The list must not be changed after this method has been called. Doing so
  /// will lead to memory leaks or exceptions.
  ///
  /// The list may contain nulls; they are ignored.
  factory Listenable.merge(List<Listenable> listenables) = _MergingListenable;

  /// Register a closure to be called when the object notifies its listeners.
  void addListener(VoidCallback listener);

  /// Remove a previously registered closure from the list of closures that the
  /// object notifies.
  void removeListener(VoidCallback listener);
}

他提供了添加观察和移除观察的方法,正好是观察者模式所需要的。至于model类中的add和remove什么时候调用我们稍后会说。而notifyListeners是我们自己手动调用的。可以看到这里主要是对version值进行了更改,并且调用了每个listener中的VoidCallback。到目前为止我们只知道通过notifyListener方法发送更新,其余的还不清除,那我们接着看第二步

ScopedModel:在顶层使用这个类包裹刚才声明的model

/// Provides a [Model] to all descendants of this Widget.
///为当前widget的子组件提供model,所以我们之前把这个放在materialApp上
/// 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.
///
/// ### Example
///
/// ```
/// ScopedModel<CounterModel>(
///   model: CounterModel(),
///   child: ScopedModelDescendant<CounterModel>(
///     builder: (context, child, model) => Text(model.counter.toString()),
///   ),
/// );
/// ```
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),
    );
  }

可以看到他其实是一个StatelessWidget,将构建页面的model以及页面child利用AnimatedBuilder进行了组装。我们先来看看AnimatedBuilder是个什么东西。

class AnimatedBuilder extends AnimatedWidget {
/// Creates an animated builder.
///
/// The [animation] and [builder] arguments must not be null.
const AnimatedBuilder({
  Key key,
  @required Listenable animation,
  @required this.builder,
  this.child,
}) : assert(animation != null),
     assert(builder != null),
     super(key: key, listenable: animation);

/// Called every time the animation changes value.
final TransitionBuilder builder;

/// The child widget to pass to the [builder].
///
/// If a [builder] callback's return value contains a subtree that does not
/// depend on the animation, it's more efficient to build that subtree once
/// instead of rebuilding it on every animation tick.
///
/// If the pre-built subtree is passed as the [child] parameter, the
/// [AnimatedBuilder] will pass it back to the [builder] function so that it
/// can be incorporated into the build.
///
/// Using this pre-built child is entirely optional, but can improve
/// performance significantly in some cases and is therefore a good practice.
final Widget child;

@override
Widget build(BuildContext context) {
  return builder(context, child);
}
}

它继承于AnimatedWidget,可以看到其中的必传参数为listenable以及builder(Widget Function(BuildContext context, Widget child);),然后只在build方法中利用传入的builder构建child,我们再继续看一下AnimatedWidget

abstract class AnimatedWidget extends StatefulWidget {
  /// Creates a widget that rebuilds when the given listenable changes.
  ///
  /// The [listenable] argument is required.
  const AnimatedWidget({
    Key key,
    @required this.listenable,
  }) : assert(listenable != null),
       super(key: key);

  /// The [Listenable] to which this widget is listening.
  ///
  /// Commonly an [Animation] or a [ChangeNotifier].
  final Listenable listenable;

  /// Override this method to build widgets that depend on the state of the
  /// listenable (e.g., the current value of the animation).
  @protected
  Widget build(BuildContext context);

  /// Subclasses typically do not override this method.
  @override
  _AnimatedState createState() => _AnimatedState();

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<Listenable>('animation', listenable));
  }
}

class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    widget.listenable.addListener(_handleChange);
  }

  @override
  void didUpdateWidget(AnimatedWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.listenable != oldWidget.listenable) {
      oldWidget.listenable.removeListener(_handleChange);
      widget.listenable.addListener(_handleChange);
    }
  }

  @override
  void dispose() {
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }

  void _handleChange() {
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }

  @override
  Widget build(BuildContext context) => widget.build(context);
}

可以看到AnimatedWidget是一个StatefulWidget,在initState时候调用了传入listenable的addListener方法,在dispose的时候调用了removeListener方法,而添加和移除的listener则为_handleChange()这个方法,也就是调用了setState,使其重新build。所以之前分析的在notifyListeners()方法中调用每个listener的VoidCallback其实就是这个_handleChange()。到这里就可以清楚的知道如何的add、remove、notify listener了。回过来还看ScopedModel这个类,刚才我们看到了AnimatedBuilder,他其中的builder是用_InheritedModel构建的,我们来看看

/// Provides [model] to its [child] [Widget] tree via [InheritedWidget].  When
/// [version] changes, all descendants who request (via
/// [BuildContext.inheritFromWidgetOfExactType]) to be rebuilt when the model
/// changes will do so.
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);
}

可以看到之前在model中定义的version派上用场了,也就是说如果两次的version如果一样那么就不触发更新。而这个InheritedWidget官方解释为

/// Base class for widgets that efficiently propagate information down the tree.

///widget的基类,可以有效的沿着widget树进行消息的传递

/// To obtain the nearest instance of a particular type of inherited widget from

/// a build context, use [BuildContext.inheritFromWidgetOfExactType].

///为了获取某一指定类型的inheritedWidget的实例,可以通过buildContext.inheritFromWidgetOfExactType

/// Inherited widgets, when referenced in this way, will cause the consumer to

/// rebuild when the inherited widget itself changes state.

/// 通过这种方式引用的widget,当他本身的状态发生改变时会让它的消费者重新build

至此前两步就分析完了,最后我们来看最后一步

ScopedModelDescendant:他的源码如下

/// Finds a specific [Model] provided by a [ScopedModel] Widget and rebuilds
/// whenever the [Model] changes.
///
/// Provides an option to disable rebuilding when the [Model] changes.
///
/// Provide a constant [child] Widget if some portion inside the builder does
/// not rely on the [Model] and should not be rebuilt.
///
/// ### Example
///
/// ```
/// ScopedModel<CounterModel>(
///   model: CounterModel(),
///   child: ScopedModelDescendant<CounterModel>(
///     child: Text('Button has been pressed:'),
///     builder: (context, child, model) {
///       return Column(
///         children: [
///           child,
///           Text('${model.counter}'),
///         ],
///       );
///     }
///   ),
/// );
/// ```
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),
    );
  }
}

可以看到这里一共就三个参数,builder,child,和rebuildOnChange(变化时是否重建)。这里的builder定义如下

typedef Widget ScopedModelDescendantBuilder<T extends Model>(
  BuildContext context,
  Widget child,
  T model,
);

其实就是利用定义好的Model以及child构建页面,这里重点看一下ScopeModel.of方法

 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;
}

这里首先获取了一下它的 Type,随后根据 rebuildOnChange 的值用 inheritFromWidgetOfExactType/ ancestorWidgetOfExactType来查找widget。 inheritFromWidgetOfExactType 是用来获取给定类型的最近的 widget,并且在值更新的时候自动重新构建。 ancestorWidgetOfExactType 是用来获取给定类型最近的 祖先 Widget,并且在值更新的时候不重新构建。 所以这样就控制住了没有必要的UI更新