mobx.dart

Dart 语言的 MobX。轻松、响应式地管理您的 Dart 和 Flutter 应用程序的状态。

通过透明函数响应式编程 (TFRP),为您的 Dart 应用中的状态管理赋能。

MobX 是一个状态管理库,它可以简化应用程序的响应式数据与 UI 的连接。这种连接是完全自动的,感觉非常自然。作为应用程序开发者,您只需纯粹关注 UI(或其他地方)需要消费哪些响应式数据,而无需担心两者之间的同步问题。

这并非魔法,但它确实很智能,能够识别哪些数据正在被消费(可观察对象)以及在哪里被消费(反应),并自动为您进行跟踪。当可观察对象发生变化时,所有的反应都会重新运行。有趣的是,这些反应可以是任何东西,从简单的控制台日志、网络调用到重新渲染 UI。

MobX 对于 JavaScript 应用来说一直是一个非常高效的库,这个 Dart 语言的移植版本旨在带来同样水平的生产力。

核心概念

MobX Triad

MobX 的核心是三个重要概念:可观察对象 (Observables)动作 (Actions)反应 (Reactions)

可观察对象

可观察对象代表了您应用程序的响应式状态。它们可以是简单的标量,也可以是复杂的对象树。通过
将应用程序的状态定义为一个可观察对象的树,您可以暴露一个供 UI
(或应用中其他观察者)消费的*响应式状态树*。

一个简单的响应式计数器由以下可观察对象表示:

import 'package:mobx/mobx.dart';

final counter = Observable(0);

更复杂的可观察对象,如类,也可以被创建。

class Counter {
  Counter() {
    increment = Action(_increment);
  }

  final _value = Observable(0);
  int get value => _value.value;

  set value(int newValue) => _value.value = newValue;
  Action increment;

  void _increment() {
    _value.value++;
  }
}

乍一看,这确实像是一些样板代码,而且可能很快就会变得难以管理!
这就是为什么我们加入了 mobx_codegen,它允许您将上面的代码替换为以下内容:

import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

abstract class CounterBase with Store {
  @observable
  int value = 0;

  @action
  void increment() {
    value++;
  }
}

请注意,这里使用了注解来标记类的可观察属性。是的,这里有一些头部的样板代码,
但对于任何类来说,这都是固定的。当您构建更复杂的类时,这些样板代码
就会显得微不足道,您将主要关注花括号内的代码。

注意:注解可通过 mobx_codegen 包获得。

只读

如果您想减少代码量,可以将 @observable 替换为 @readonly
它会为每个私有变量生成一个公共的 getter,这样您的 store 的客户端
就无法更改其值。在这里阅读更多相关信息:链接

计算可观察对象

可以派生的东西,就应该自动派生。

您应用程序的状态由*核心状态*和*派生状态*组成。*核心状态*是您所处理的领域固有的状态。例如,如果您有一个 `Contact` 实体,`firstName` 和 `lastName` 就构成了 `Contact` 的*核心状态*。然而,`fullName` 是*派生状态*,是通过组合 `firstName` 和 `lastName` 得到的。

这种依赖于*核心状态*或*其他派生状态*的*派生状态*,被称为计算可观察对象 (Computed Observable)。当其底层的可观察对象发生变化时,它会自动保持同步。

MobX 中的状态 = 核心状态 + 派生状态

import 'package:mobx/mobx.dart';

part 'contact.g.dart';

class Contact = ContactBase with _$Contact;

abstract class ContactBase with Store {
  @observable
  String firstName;

  @observable
  String lastName;

  @computed
  String get fullName => '$firstName, $lastName';

}

在上面的例子中,如果 `firstName` 和/或 `lastName` 发生变化,`fullName` 会自动保持同步。

操作

动作是您改变可观察对象的方式。动作并非直接修改它们,而是
为修改操作增加了语义。例如,执行一个 `increment()` 动作比简单地执行 `value++`
更有意义。此外,动作还会将
所有的通知批量处理,并确保只有在动作完成后才会通知变更。
因此,观察者只会在动作原子性地完成后才会收到通知。

请注意,动作也可以嵌套,在这种情况下,通知会在
最顶层的动作完成后发出。

final counter = Observable(0);

final increment = Action((){
  counter.value++;
});

在类中创建动作时,您可以利用注解的优势!

import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

abstract class CounterBase with Store {
  @observable
  int value = 0;

  @action
  void increment() {
    value++;
  }
}

异步动作

MobX.dart 自动处理异步动作,不需要用 `runInAction` 来包装代码。

@observable
String stuff = '';

@observable
loading = false;

@action
Future<void> loadStuff() async {
  loading = true; //This notifies observers
  stuff = await fetchStuff();
  loading = false; //This also notifies observers
}

反应

反应完成了 MobX 的**可观察对象**、**动作**和**反应**三要素。它们是
响应式系统的观察者,每当它们所跟踪的可观察对象
发生变化时,它们就会收到通知。反应有以下几种类型。所有这些
都会返回一个 `ReactionDisposer`,这是一个可以被调用以销毁反应的函数。

反应的一个*显著特点*是它们*自动跟踪*所有可观察对象,无需任何显式连接。在反应中*读取一个可观察对象*的行为就足以跟踪它!

您用 MobX 编写的代码几乎没有任何冗余的仪式代码!

ReactionDisposer autorun(Function(Reaction) fn)

立即运行反应,并在 `fn` 内部使用的任何可观察对象发生变化时再次运行。
fn.

import 'package:mobx/mobx.dart';

String greeting = Observable('Hello World');

final dispose = autorun((_){
  print(greeting.value);
});

greeting.value = 'Hello MobX';

// Done with the autorun()
dispose();


// Prints:
// Hello World
// Hello MobX

ReactionDisposer reaction<T>(T Function(Reaction) predicate, void Function(T) effect)

监视 `predicate()` 函数内部使用的可观察对象,并在 `predicate` 返回不同值时运行 `effect()`。
只有 `predicate()` 内部的可观察对象会被跟踪。

import 'package:mobx/mobx.dart';

String greeting = Observable('Hello World');

final dispose = reaction((_) => greeting.value, (msg) => print(msg));

greeting.value = 'Hello MobX'; // Cause a change

// Done with the reaction()
dispose();


// Prints:
// Hello MobX

ReactionDisposer when(bool Function(Reaction) predicate, void Function() effect)

监视 `predicate()` 内部使用的可观察对象,并在其返回 `true` *时*运行 `effect()`。`effect()` 运行后,`when` 会自动销毁。所以您可以将 *when* 看作是*一次性*的 `reaction`。您也可以提前销毁 `when()`。

import 'package:mobx/mobx.dart';

String greeting = Observable('Hello World');

final dispose = when((_) => greeting.value == 'Hello MobX', () => print('Someone greeted MobX'));

greeting.value = 'Hello MobX'; // Causes a change, runs effect and disposes


// Prints:
// Someone greeted MobX

Future<void> asyncWhen(bool Function(Reaction) predicate)

与 `when` 类似,但返回一个 `Future`,该 `Future` 在 `predicate()` 返回 *true* 时完成。这是一种等待 `predicate()` 变为 `true` 的便捷方式。

final completed = Observable(false);

void waitForCompletion() async {
  await asyncWhen(() => _completed.value == true);

  print('Completed');
}

Observer

应用中最直观的反应之一是 UI。Observer 小组件(属于 `flutter_mobx` 包的一部分)为其 `builder` 函数中使用的可观察对象提供了一个精细的观察者。每当这些可观察对象发生变化,`Observer` 就会重新构建和渲染。

下面是完整的*计数器*示例。

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

abstract class CounterBase with Store {
  @observable
  int value = 0;

  @action
  void increment() {
    value++;
  }
}

class CounterExample extends StatefulWidget {
  const CounterExample({Key key}) : super(key: key);

  @override
  _CounterExampleState createState() => _CounterExampleState();
}

class _CounterExampleState extends State<CounterExample> {
  final _counter = Counter();

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: const Text('Counter'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'You have pushed the button this many times:',
              ),
              Observer(
                  builder: (_) => Text(
                        '${_counter.value}',
                        style: const TextStyle(fontSize: 20),
                      )),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _counter.increment,
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      );
}

GitHub

https://github.com/mobxjs/mobx.dart