MobX とは
MobX は JavaScript の状態管理ライブラリで、React などのフレームワークと組み合わせて使用することが多く、特徴は状態管理をシンプルで直感的に行うことができる点である。以下の 3 つの主要な概念に基づいている。
- State
アプリケーションを動かすデータのこと。スプレッドシートのセルのように扱うことで、その変更を追跡する。 - Actions
State を変更するコード。ユーザーイベント、バックエンドデータへの送信、スケジュールされたイベントなどが該当します。 - Derivations
State から相互作用なしに導き出すことができるもの。ユーザーインターフェース、導出データ(残りの todo の数など)、バックエンドの統合(サーバーへの変更の送信など)が該当します。
原則
MobX は、action が state を変更し、それによって影響を受けるすべてのビューが更新されるという一方向のデータフローを使用する。
- すべてのderivationは、state が変化すると自動的に更新される。
- デフォルトではすべてのderivationは同期的に更新される。例えば state を変更した後に、action が computed value を直接安全にみれることを意味する。
- computed values 遅延的に更新される。アクティブに使用されていない computed value は、副作用(I/O)で必要になるまで更新されない。
- すべての computed value は純粋でなければなく、computed value は state を変更することはない。
使い方
1. State を定義して観測可能にする
プリミティブやオブジェクトや配列など好きなデータを State にできる。変化させたい要素を MobX が観測できるようにするためにobservable
として設定する。
import { makeObservable, observable, action } from "mobx";
class Todo {
id = Math.random();
title = "";
finished = false;
constructor(title: string) {
makeObservable(this, {
title: observable, // state
finished: observable, // state
toggle: action, // action
});
this.title = title;
}
// actionでstateを変更している
toggle() {
this.finished = !this.finished;
}
}
2. Actions を使って状態を更新する
Actions とは、State を変更するコードの一部であり、ユーザーイベント、バックエンドへのデータプッシュ、スケジュールされたイベントなどのこと。Actions は、ユーザーがスプレッドシートのセルに新しい値を入力するようなものである。
上のTodo
クラスでは、finished
の値を変更するtoggle
メソッドが action である。observable
を変更するコードはすべてaction
としてマークすることを推奨。そうすることで、MobX は自動的にトランザクションを適用し、最適なパフォーマンスを得ることができる。actions を使用することで、コードを構造化することができ、意図しないときに誤って状態を変更してしまうことを防ぐことができる。
3. State の変化に自動的に対応する Derivations を作成する
State から導出されるものはすべて Derivations である。Derivations は 2 種類に分けられる。
- Computed values
observable の State から計算して導出される値。観測している State が変更されると自動的に再計算される。 - Reactions
State が変更されたときに自動的に発出する必要がある動作、副作用。
MobX を使い始めると Reactions を使いがちになるが、現在の State に基づいて値を作成したい場合は、常に computed values を使用すること。
3.1 Computed values の使い方
computed values を作成するには、getter 関数を使用してプロパティを定義し、makeObservable で computed としてマークする。
import { makeObservable, observable, computed } from "mobx";
class TodoList {
todos: Todo[] = [];
constructor(todos: Todo[]) {
makeObservable(this, {
todos: observable, // state
unfinishedTodoCount: computed, // computed value
});
this.todos = todos;
}
// stateを使用して新たな値を計算している = computed value
get unfinishedTodoCount() {
return this.todos.filter((todo) => !todo.finished).length;
}
}
todo が追加されたときやfinished
プロパティが変更されたときに、unfinishedTodoCount
が自動的に更新される。
スプレッドシートの数式みたいなもの。自動的に更新されるが、必要なときだけ更新がされる。
3.2 Reactions の使い方
ユーザが画面上で State や Computed values の変化を見ることができるようにするには、GUI の一部を再描画する reaction が必要。
reaction は computed values と似ているが、情報を生成する代わりに、console.log への出力、ネットワークリクエスト、React コンポーネントツリーの増分更新などの副作用を生成する。
最もよく使われる reaction は UI コンポーネント。action と reaction の両方から副作用を引き起こすことが可能であることに注意。フォーム送信時のネットワークリクエストのような、トリガー元が明確で明示的な副作用は、関連するイベントハンドラから明示的にトリガーされるべき。
3.3 リアクティブな React components
React の場合、Observer
タグを使うすることでコンポーネントをリアクティブにすることができる。Observer
タグは子要素を関数として受け取り、その関数の戻り値をレンダリングする。関数内の State が変更されると、関数が再評価され、結果が再レンダリングされる。
"use client";
import { Observer } from "mobx-react-lite";
const todoList = new TodoList([
new Todo("Get coffee"),
new Todo("Write simpler code"),
]);
const TodoView = ({ todo }: { todo: Todo }) => (
<Observer>
{() => (
<li>
<input
type="checkbox"
checked={todo.finished}
onClick={() => todo.toggle()}
/>
{todo.title}
</li>
)}
</Observer>
);
const TodoListView = () => {
return (
<Observer>
{() => (
<div>
<ul>
{todoList.todos.map((todo) => (
<Observer key={todo.id}>
{() => <TodoView todo={todo} />}
</Observer>
))}
</ul>
Tasks left: {todoList.unfinishedTodoCount}
</div>
)}
</Observer>
);
};
export default TodoListView;
上記の例の onClick ハンドラは、toggle
action を使用しているため、TodoView
コンポーネントを再レンダリングさせるが、未完了タスクの数が変化した場合にのみTodoListView
コンポーネントを再レンダリングさせる。Tasks left
の行を削除すれば(または別のコンポーネントに入れれば)、タスクにチェックを入れてもTodoListView
コンポーネントは再レンダリングされなくなる。