deepblue-will’s diary

JS、CSS,Ruby、Railsなど仕事や趣味で試した技術系のことを書いていきます。

Angularの状態管理にMobXはいかがでしょうか?

Angular Advent Calendar 2018 19日目の記事です!

Angularの状態管理といえば、NgRxが一般的ですが、最近色々選択肢もいろいろでてきました。NgRxのReduxのような状態管理を行わずに、AngularのシングルトンなServiceを利用してRxJSのみ行うこともできますし、Akita のようなシンプルな状態管理のライブラリもでてます。(Angularのシンプルな状態管理ライブラリ Akita について - Qiita で紹介されてます!)

そこで今回はMobX というよりシンプルで簡単な状態管理のライブラリを紹介したいと思います。

MobXとは

Introduction | MobX

Simple, scalable state management

公式サイトでこう謳ってるとおり、とてもシンプルだけど拡張性の高い状態管理のライブラリです。 MobX公式のフロー図はこのような形です。

mobx flow ref: https://mobx.js.org

  • Action
    • stateを更新する関数。stateはActionからのみ更新できる(オプション)
  • State
    • 状態管理するObject。Stateの集合を Store という単位で管理するのが一般的
  • Computed
    • stateを加工するgetter。Stateが更新さえると自動的に計算される
  • Reactions
    • Stateの変更を検知して、別のActionを実行したり、処理を実行したりできる

サンプルコードでわかるAngular + MobX

基本的なところをサンプルコードを交えて説明したいと思います。 angular-mobxはサンプルコードを公開してくれているので、これで説明したいと思います。

@ observable

observable | MobX

import { observable } from 'mobx';
@observable todos = [];

状態管理したいObjectに @observable というDecoratorを付与することで、監視対象にすることができます。 この値が変更されると、後述する @computed の値が更新されたり、 autorun が実行されたりします。 また、mobx-angular*mobxAutorun ディレクティブなどと合わせて使用することにより、更新後自動的にChange Detectionが走ります

@Action

action | MobX

import { action } from 'mobx';
@action addTodo({ title, completed = false }) {
  this.todos.push(new Todo({ title, completed }));
}

// actionには名前をつけられる
// https://github.com/mobxjs/mobx-devtools などで発火するActionがみやすくなる
@action('remove todo')
removeTodo(todo) {
  const index = this.todos.indexOf(todo);
  this.todos.splice(index, 1);
}

@action はStateを操作するためのメソッドに付与します。 MobXのデフォルトの設定ではStateはどこからでも更新が可能です。が、より厳格なState管理をするためにもStateはActionからのみの更新にしたいものです。そのためには以下の設定をいれることで可能になります。

import { enforceActions } from 'mobx';
configure({ enforceActions: 'always' });

なお、上記の設定にすると@actionのメソッド内でも非同期処理場合はstateを直接更新できません。その場合は runInAction() をあわせて使います。

@action('add todo')
async addTodo(todo: any) {
  const result = await this.http.post('/api/todo', todo)
  runInAction('add todo success', () => {
    this.todo.push(result)
  })
}

@computed

(@)computed | MobX

import { computed } from 'mobx';
@computed get filteredTodos() {
  return this.filter !== 'SHOW_ALL' ?
    this._filter(this.todos, this.filter) :
    this.todos;
}

Stateを計算した新しい値を返却するgetterに付与します。公式サイトの説明ではスプレットシートの関数のようなものといっています。 あるstateが変更されれば自動的に計算され、値を取得することができます。

autorun

autorun | MobX

import { autorun } from 'mobx';
autorun(() => {
  localStorage.todos = JSON.stringify(toJS(this.todos));
  localStorage.filter = JSON.stringify(toJS(this.filter));
});

autorun はコールバック内のstateが変更されたら自動的に実行されます。これにより、stateの副作用を自動的に解決できるので、state操作のみを中注することができます。autorun は定義時にも実行され、その時実行されたstateを監視対象にする動きのようです。 似たようなものに reactionwhenがあります

MobXの良いところ悪いところ

良いところ

  • 必要なファイル数が少ない
    • Reduxみたいに一つの操作をするために何個もファイルを編集する必要がない
  • 習得のハードルが低い
    • フロントエンド専任でないエンジニアでもすぐ仕組みを理解してコードを書き始められた
  • 副作用を勝手に解決してくれるので気にするべき値の操作を限定できる
    • computedautorun など
  • コントリビューターが多い。スター数も多い(※ MobX)
    • 死なないライブラリだと使うの安心できる

悪いところ

  • 自由な反面、決まったルールがほとんどないのでちゃんとした設計が必要
    • Storeの分割やComponentへの接続方法
    • 副作用の解決方法が点在しているどこでどう更新されるのかあとからわからなくなる
  • Angular + MobXの情報がほとんどない

最後に

egghead.ioでReactでの例ではありますが簡単なMobXのチュートリアルが公開されてます。興味がある方はぜひ見てみてください。