美文网首页Flutter圈子Flutter中文社区Android开发
从源码解析 MobX 响应式刷新机制

从源码解析 MobX 响应式刷新机制

作者: 岛上码农 | 来源:发表于2022-03-11 21:31 被阅读0次

前言

MobX 的设计似乎很神奇,感觉使用了一个 Observer 后就能自动跟踪状态对象的变化,实现响应式刷新。这个具体是如何实现的呢?我们来从源码梳理一下。

Observer 类

Observer 类的类关系图如下图所示。

MobX 核心类关系

这里面有几个关键的类,我们一一进行介绍。

StatelessObserverWidget

abstract class StatelessObserverWidget extends StatelessWidget
    with ObserverWidgetMixin {
  /// Initializes [key], [context] and [name] for subclasses.
  const StatelessObserverWidget(
      {Key? key, ReactiveContext? context, String? name})
      : _name = name,
        _context = context,
        super(key: key);

  final String? _name;
  final ReactiveContext? _context;

  @override
  String getName() => _name ?? '$this';

  @override
  ReactiveContext getContext() => _context ?? super.getContext();

  @override
  StatelessObserverElement createElement() => StatelessObserverElement(this);
}

这里的 createElement 覆盖了 StatelessWidget 的方法,返回的是一个StatelessObserverElement对象,目的是用于控制Element的刷新。

ObserverWidgetMixin

这是一个用于 Widgetmixin,主要的用途是使用 createReaction 方法创建 reaction,以便在状态改变的时候调用对应的方法。这个 createReaction 实际是在ObserverElementMixin中被调用的。

mixin ObserverWidgetMixin on Widget {
  String getName();

  ReactiveContext getContext() => mainContext;

  @visibleForTesting
  Reaction createReaction(
    Function() onInvalidate, {
    Function(Object, Reaction)? onError,
  }) =>
      ReactionImpl(
        getContext(),
        onInvalidate,
        name: getName(),
        onError: onError,
      );

  void log(String msg) {
    debugPrint(msg);
  }
}

StatelessObserverElement

StatelessObserverElement这个其实就是一个特殊的 StatelessElement,关于 Element的介绍可以看之前本专栏关于渲染机制的篇章:Flutter 入门与实战(三十九):渲染模式详解。这个类仅仅是混入了ObserverElementMixin。所有特殊的业务都是在ObserverElementMixin中实现的,我们来看看ObserverElementMixin的源码。

mixin ObserverElementMixin on ComponentElement {
  ReactionImpl get reaction => _reaction;
  late ReactionImpl _reaction;

  // Not using the original `widget` getter as it would otherwise make the mixin
  // impossible to use
  ObserverWidgetMixin get _widget => widget as ObserverWidgetMixin;

  @override
  void mount(Element? parent, dynamic newSlot) {
    _reaction = _widget.createReaction(invalidate, onError: (e, _) {
      FlutterError.reportError(FlutterErrorDetails(
        library: 'flutter_mobx',
        exception: e,
        stack: e is Error ? e.stackTrace : null,
      ));
    }) as ReactionImpl;
    super.mount(parent, newSlot);
  }

  void invalidate() => markNeedsBuild();

  @override
  Widget build() {
    late Widget built;

    reaction.track(() {
      built = super.build();
    });

    if (!reaction.hasObservables) {
      _widget.log(
        'No observables detected in the build method of ${reaction.name}',
      );
    }

    return built;
  }

  @override
  void unmount() {
    reaction.dispose();
    super.unmount();
  }
}

可以看到,这个 mixin 重载了 Elemenntmount 方法,在mount 里面创建了 reaction,其中响应的方法是invalidate,而 invalidate 方法实际上就是markNeedsBuild方法。也就是说状态数据发生改变的时候,实际上会通过 reaction 来调用markNeedsBuild通知Element刷新,这个方法实际上会触发 Widgetbuild 方法。关于markNeedsBuild这个方法本专栏在之前的篇章都有介绍:

在这个 mixin 里面还重载了 build方。这里调用了reactiontrack方法。一层层跟踪下去,实际上是这里主要的目的是将observer对象和其依赖(其实也就是Observerbuilder返回的widget)进行绑定。

void _bindDependencies(Derivation derivation) {
    final staleObservables =
        derivation._observables.difference(derivation._newObservables!);
    final newObservables =
        derivation._newObservables!.difference(derivation._observables);
    var lowestNewDerivationState = DerivationState.upToDate;

    // Add newly found observables
    for (final observable in newObservables) {
      observable._addObserver(derivation);

      // Computed = Observable + Derivation
      if (observable is Computed) {
        if (observable._dependenciesState.index >
            lowestNewDerivationState.index) {
          lowestNewDerivationState = observable._dependenciesState;
        }
      }
    }

    // Remove previous observables
    for (final ob in staleObservables) {
      ob._removeObserver(derivation);
    }

    if (lowestNewDerivationState != DerivationState.upToDate) {
      derivation
        .._dependenciesState = lowestNewDerivationState
        .._onBecomeStale();
    }

    derivation
      .._observables = derivation._newObservables!
      .._newObservables = {}; // No need for newObservables beyond this point
  }

这条线基本上就理完了,那具体又是怎么精准跟踪的呢?我们来看看 MobX 生成的那部分代码。

状态对象跟踪

生成的代码里面,带有@observable注解的成员生成代码如下:

final _$praiseCountAtom = Atom(name: 'ShareStoreBase.praiseCount');

@override
int get praiseCount {
  _$praiseCountAtom.reportRead();
  return super.praiseCount;
}

@override
set praiseCount(int value) {
  _$praiseCountAtom.reportWrite(value, super.praiseCount, () {
    super.praiseCount = value;
  });
}

这里面关键在于 get 方法中调用了Atom类的reportRead方法。实际上最终调用的是_reportObserved方法。这个方法其实就是将之前Observer绑定的依赖和对应的状态对象属性关联起来。因此,才能够实现状态对象的某个属性更新时,只更新依赖该属性的组件,实现精准更新。

void _reportObserved(Atom atom) {
  final derivation = _state.trackingDerivation;

  if (derivation != null) {
    derivation._newObservables!.add(atom);
    if (!atom._isBeingObserved) {
      atom
        .._isBeingObserved = true
        .._notifyOnBecomeObserved();
    }
  }
}

接下来来看 set 方法。set 方法其实就是改变了状态对象的属性,这里调用了Atom类的reportWrite方法。这会触发下面的reaction调度方法:

 void schedule() {
  if (_isScheduled) {
    return;
  }

  _isScheduled = true;
  _context
    ..addPendingReaction(this)
    ..runReactions();
}

这个调度方法最终会执行reaction_run 方法,这里面我们看到了执行了_onInvalidate 方法,这个方法正是在ObserverElementMixincreateReaction的时候传进来的,这个方法会触发 Widgetbuild

void _run() {
  if (_isDisposed) {
    return;
  }

  _context.startBatch();

  _isScheduled = false;

  if (_context._shouldCompute(this)) {
    try {
      _onInvalidate();
    } on Object catch (e, s) {
      // Note: "on Object" accounts for both Error and Exception
      _errorValue = MobXCaughtException(e, stackTrace: s);
      _reportException(_errorValue!);
    }
  }

  _context.endBatch();
}

由此我们得知了状态对象改变的时候是如何进行刷新的。

总结

整个过程我们跟踪一下,实际MobX 完成无感知响应的方式如下:

  • 控制渲染的 ElementStatelessObserverElement,该类在mount 阶段通过createReaction注册了 reaction
  • StatelessObserverElementbuild 方法中reactionobservable进行绑定。
  • Observer 中读取状态对象属性时,会调用到其 get 方法,该方法会将状态对象属性与对应的 Observer组件 进行绑定。
  • 当状态对象的属性被 set 更改的时候,会调度到该属性绑定的reaction,执行_onInvalidate方法来进行刷新,从而实现了响应式的无感知刷新。

当然这只是我们简单的分析,实际 MobX实现的细节还有更多,有兴趣的同学也可以深入了解其设计思想。

相关文章

  • 从源码解析 MobX 响应式刷新机制

    前言 MobX 的设计似乎很神奇,感觉使用了一个 Observer 后就能自动跟踪状态对象的变化,实现响应式刷新。...

  • Android博文收集

    基础理解:Activity启动流程Android屏幕刷新机制Bitmap优化详谈 机制源码解析:RecyclerV...

  • Android事件分发机制完全解析

    Android事件分发机制完全解析,带你从源码的角度彻底理解(上)Android事件分发机制完全解析,带你从源码的...

  • mobx简介

    官方文档-传送 MobX是响应式编程,实现状态的存储和管理。使用MobX将应用变成响应式可归纳为三部曲: 定义状态...

  • React Native 中 MobX使用

    MobX 通过透明的函数响应式编程(transparently applying functional react...

  • Mobx入门(一)

    Mobx是什么 MobX实现简单、可扩展的状态管理。 使用MobX将应用变成响应式可归纳为三部曲: 定义状态并使其...

  • Node.js源码解析-HTTP请求响应过程

    Node.js源码解析-HTTP请求响应过程 欢迎来我的博客阅读:《Node.js源码解析-HTTP请求响应过程》...

  • Vue 和 React 比较

    Vue 更加轻量 20kb min + gzip 渐进式框架 响应式的更新机制(视图根据数据的改变自动刷新) 学习...

  • 美团 EasyReact 源码剖析:图论与响应式编程

    美团 EasyReact 源码剖析:图论与响应式编程 美团 EasyReact 源码剖析:图论与响应式编程

  • mobx 入门

    mobx 响应式状态管理库 安装 基础概念 所谓的响应式,既是将原有数据结构,例如 数组,对象等转变为可观察对象,...

网友评论

    本文标题:从源码解析 MobX 响应式刷新机制

    本文链接:https://www.haomeiwen.com/subject/csphdrtx.html