Flutter 状态管理框架Scoped Model源码分析

本文深入分析了Flutter状态管理框架ScopedModel的源码,详细解释了ScopedModel的工作原理,包括Model类的设计、数据变化通知机制以及ScopedModel与ScopedModelDescendant的交互过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Flutter状态管理ScopedModel源码分析

Scoped Model是什么

不管你是Android开发还是iOS开发,都涉及到状态管理,Flutter开发的状态管理框架主要有

Redux
Bloc
Scoped Model

本博主要对Scoped Model的源码进行分析,Scoped Model是dart第三方开源库,它能让你无视Widget层级的深度,直接访问父Widget乃至祖先Widget的数据模型,并且在数据模型发生变化时重新渲染访问该模型的所有子Widget。

在分析源码之前我们先想一想,如果我们自己开发一个状态管理框架,如耳熟能详的MVVM框架,模型变化时要通知模型访问者,很自然的就想到了设计模式中的观察者模式,Scoped Model框架设计思想也不例外,首先我们来看看Scoped Model框架怎么使用的

Scoped Model的使用

首先在pubspec.yaml文件加入

dependencies:
scoped_model: 1.0.1

packages get后就可以使用了

  @override
  Widget build(BuildContext context) {
    super.build(context);

    return ScopedModel(
      model: _vm,
      child: ScopedModelDescendant<HomePageViewModel>(
        builder: (BuildContext content, _, __) {
          return _buildContentList();
        },
      ),
    );
  }

首次或模型变化时就会调用builder重新渲染页面,_vm 表示的是Model 状态管理类,_vm的一般在initState中初始化

  class HomePageViewModel extends Model {
     //此处省略2万行
  }

  @override
  void initState() {
    super.initState();

    _vm = HomePageViewModel();
    _vm.reloadData();

    //WidgetsBinding.instance.addObserver(this);
  }

使用方式很简单, 接下来就到了本博的重点源码分析

源码分析

框架只有一个scoped_model.dart文件, 我们先来分析下Model类

abstract class Model extends Listenable {

Model是一个抽象类,需要我们派生实现它
Listenable是Flutter框架自带的抽象接口,主要用来提供添加和移除观察者对象的接口, 定义如下

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负责实现该接口,具体实现很简单,使用Set来管理观察者

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

  /// [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);
  }

数据模型变化时如何通知订阅者呢,notifyListeners方法就是用来通知订阅者,我们数据发生变化,如果需要通知订阅者重新渲染,必须调用该方法,如果遇到数据变化,订阅者(Widget)没有重新渲染界面,一般都是没有调用notifyListeners惹得锅。

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

主要通知所有的订阅者数据的更新,没什么好说的,可能有点疑惑的就是_microtaskVersion, _version这两变量的作用,它们是用于防止多次调用notifyListeners方法导致的多次渲染页面浪费cpu,刷新页面是异步的,notifyListeners方法调用完不会立即刷新页面,下一次进入looper时才会通知订阅者
分析完了Model,我们来看看怎么使用它

 class CounterModel extends Model {
   int _counter = 0;

   int get counter => _counter;

   void increment() {
     // First, increment the counter
     _counter++;

     // Then notify all the listeners.
     notifyListeners();
  }
 }

接下来我们分析下ScopedModel

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

从定义我们看出ScopedModel是一个无状态的Widget
build方法只返回来AnimatedBuilder,AnimatedBuilder 专门用来处理动画,将动画 和要作用的 Widget 关联起来,我们看看它的构造函数

const AnimatedBuilder({
    Key key,
    @required Listenable animation,
    @required this.builder,
    this.child,
  }) 

参数animation是一个Listenable类型,当它发生变化时会调用builder重新渲染界面
build方法传的参数是Model,Model也是一个Listenable类型,这里我们就知道了为什么模型变化时,订阅者为何能刷新界面了,builder方法即为重新渲染的内容

 builder: (context, _) => _InheritedModel<T>(model: model, child: child),
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);
}

_InheritedModel派生自InheritedWidget,InheritedWidget是一个特殊的widget,可以存储和获取数据,子孙组件可以通过Element链获取到存储的数据,数据变化是否通知订阅者重新渲染页面取决于updateShouldNotify方法,version用与和上一次更新的version比较,为了避免一个方法中多次调用updateShouldNotify导致重复刷新页面浪费CPU, version如果不同就通知订阅者(子孙Widget)重新渲染页面.
子widget到底是在哪订阅的呢。其实订阅的部分都隐没在框架里,不需要我们手动订阅。让我们来瞧一瞧其真面目。

在使用ScopedModel的使用用到ScopedModelDescendant,先看下其源码

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

源码很少,rebuildOnChange 默认为true,表示model变化时会通知builder方法重建widget,否则model变化时不会调用builder重建, Widget build(BuildContext context)方法中,调用了 ScopedModel.of(context, rebuildOnChange: rebuildOnChange)获取Model数据,并将它传给builder方法,我们着重看下该方法

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

方法主要是通过订阅widget 访问祖先widget查找到_InheritedModel,然后获取其model,如果找不到就报错,rebuildOnChange的值不同调用查找的方法不同,我们先看下rebuildOnChange为true时,这时调用context.inheritFromWidgetOfExactType(type),它就是用来订阅model变化时方法,

  @override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

订阅的地方是

return inheritFromElement(ancestor, aspect: aspect);

  @override
  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    return dependOnInheritedElement(ancestor, aspect: aspect);
  }
  
  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }

主要订阅代码

_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);

这些都是flutter框架代码,有兴趣的大家自己研究下,rebuildOnChange为false时,调用的是

context.ancestorWidgetOfExactType(type);

它的作用仅仅访问但不订阅InheritedWidget的变化,具体源码如下

  @override
  Widget ancestorWidgetOfExactType(Type targetType) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element ancestor = _parent;
    while (ancestor != null && ancestor.widget.runtimeType != targetType)
      ancestor = ancestor._parent;
    return ancestor?.widget;
  }

Widget都有Element与之对应,Widget只是配置Element的数据,Widget每次重建Element却不一定重建,它只是根据配置(Widget)来进行Element刷新重建和销毁,子Element节点可以访问父Element,通过这种方式,不管Widget层级多深都能访问到祖先节点的数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值