Flutter Mobx是一个用于状态管理的第三方库,它可以帮助Flutter开发者更方便地管理应用中的状态。它的原理是基于响应式编程和观察者模式。下面分别介绍一下这两个概念。
响应式编程
响应式编程是一种编程范式,它的核心思想是数据流动。当数据发生变化时,程序会自动更新相关的UI界面。Flutter中的Stream就是一个很好的响应式编程示例。Mobx也是基于这个思想来实现状态管理的。
观察者模式
观察者模式是一种设计模式,它定义了对象间的一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都会得到通知并自动更新。Mobx就是基于这个模式来实现状态管理的。当Mobx中的被观察对象发生变化时,所有观察它的对象都会得到通知并更新UI界面。
综上所述,Flutter Mobx的状态管理原理就是基于响应式编程和观察者模式实现的。这种设计可以帮助开发者更方便地管理状态,提高开发效率。
整个Mobx框架我们在用下来的过程中发现,需要我们处理的地方很少,只需要我们定义observerable数据源对象,在最外层使用Observer控件去监听对象;整个的使用体验相对于其它状态管理框架要简单方便很多,但原理理解起来也比较费时费力。
Mobx数据绑定变化时序图
在开始分析前先上个时序图,方便后续理解;
1.0ObserverElementMixin 关键类
Observer类它继承了StatelessObserverWidget 类,StatelessObserverWidget 类其中关键的点是它with了 ObserverWidgetMixin ;而ObserverWidgetMixin 它是继承于系统控件Widget
的;并且重写了Widget中的createElement 方法;
从上面看到createElement 方法返回了一个StatelessObserverElement 对象,而它又继承了StatelessElement 类,并且with ObserverElementMixin 这个类;而ObserverElementMixin
是我们数据源绑定的入口,是这个类让我们所有的数据源监听都是在这个类中去实现。
可以看到ObserverElementMixin 重写了build方法;数据源的绑定就是在build方法中的reaction.track方法去实现的。reaction对象是什么时候创建的呢?
1.1 Reaction 的创建
ObserverElementMixin类mount方法中会创建出一个Reaction实例类,每个Observer对象都对应的会产生一个Reaction实例;Reaction类的构造方法中第一个参数在最终更新UI时会调用到,这里的入参invalidate 就是一个更新UI的方法。
ObserverElementMixin
在创建完reaction实例对象后,在build方法中会调用track方法,在track方法内部进行数据源的绑定;这里关键的点是super.build()方法调用,调用super.build()方法后会进行UI的渲染(这一步很关键);
ObserverElementMixin
1.2 Reaction 中数据源的绑定
从上面reaction.track()方法调用后,在track方法内部是如何绑定数据源的呢?在track方法内部用到了一个Mobx系统中上下方context对象;在这个上下文中它知道各种可观察量及其相关反应;默认情况下,在Mobx中有一个mainContext对象,它是一个单例对象;它管理着所有的Reaction实例。track方法内部最重要的方法其实是调用了context中的trackDerivation方法;
ReactionImpl
1.3 ReactiveContext 上下文
这里的Context上下文不是指Flutter系统中的Context,是Mobx系统中定义的一个上下文类;它管理着所有的Reaction实例,所有UI的变更也是通过Context去进行分发的。
1.3.1 ReactiveContext 对象的创建
ObserverWidgetMixin
在Reaction初始化的时候调用getContext()方法去创建了ReactiveContext对象,从代码可以看出mainContext是一个final修饰的,只能被赋值一次,所以可以把它认为是一个单例对象;
1.3.2 context.trackDerivation(``Derivation d, T Function() fn)
回到1.2中_context.trackDerivation(this,fn)方法的调用;方法内部调用了外部传递过来的fn()方法,而这个也是在这里设计最巧妙的地方,fn()方法的调用其实是最外面的super.build()方法的调用;而在这里重新去build了一遍UI;为什么需要去build一次UI呢?因为只有先build一次,才会去渲染UI,渲染UI的时候才会去get数据源,从而绑定数据源。
ObserverWidgetMixin
1.4 Atom 对象的创建
我们都知道每个需要被观察的对象都需要添加上observable 注释,并最终会生成一个.g.dart的文件出来,而在这个文件中会发现每个添加了observable 注释的数据源都是一个atom对象;
1.3.2中讲到会重新去build一次UI;而build一次时,我们调用get方法去拿我们需要的数据源;而get方法中Atom中的reportRead()方法,而这个方法就是让reaction去收集当前observer(一个observer控件对象一个reaction对象)控件中所有被observable注释的数据源;
AtomSpyReporter
Atom
ReactiveContext
可以看到最终调用到ReactiveContext中的_reportObserved方法,入参就是当前的数据源atom;而derivation对象是我们在1.2中调用trackDerivation中传递的入参this,即当前的reaction实例对象;这样最终每一个数据源atom的调用get方法都其添加到reaction实例的newObservables对象中存储起来了。
1.5 数据绑定
上面从1.2中可以看到在fn()方法调用后,我们重新build数据,将Observer控件下面所有的标记观察的对象都添加到了当前reaction实例的newObservables中保存起来了;而在fn()方法调用后,我们看到后面以调用了_endTracking() 方法;
ReactiveContext
这里真正将数据源与UI进行绑定的是_bindDependencies方法;在这个方法中会遍历之前所有的缓存的数据源Atom并将每个Atom绑定到观察者上;方法入参derivation就是相应的reaction实例;一个Atom可能不只绑定到一个derivation上,如果多个Observer控件共用一个数据源Atom;那么就有可能会有多个derivation绑定。
Atom
1.6 数据变化监听
从上面可以看到一个完整的数据绑定过程了,那当绑定的数据源变化时,又是如果通知页面更新的呢?看下Atom数据的更新方法,可以看到调用了Atom中的reportWrite方法;而方法内部调用的是
reportChange方法;
Atom
一直往下看,可以发现最终调用的是context中的propagateChange方法;而这个方法的内部调用的就是遍历atom中绑定的derivation,其实就是reaction对象;
ReactiveContext
而这里Atom中的保存的_observers对象就是我们之前绑定的reaction对象,遍历调用reaction中的_onBecomeStale方法;
ReactionImpl
ReactiveContext
可以看到在context中最终调用的当前reaction对象的_run()方法;在_run方法中最后调用的是_onInvalidate() 方法,这个方法就是我们在1.1中创建ReactionImpl 时传递进来的invalidate
方法;
ReactionImpl
ObserverElementMixin
回到1.1中Reactinon对象创建的时候,可以看到invalidate 方法最终调用的是markNeedsBuild 方法,页面进行重新渲染。至此,数据发生变更 后页面完成了重新渲染。