MobXの要点
概念
MobXは、アプリケーション内で以下の3つの概念を区別します。
- 状態
- アクション
- 派生
以下の各概念を詳しく見ていきましょう。または、MobXとReactの10分間入門では、これらの概念をステップバイステップでインタラクティブに詳しく掘り下げ、簡単なTodoリストアプリを構築できます。
以下の概念説明の中に「シグナル」の概念を認識する方もいるかもしれません。その通りです。MobXは、まさにシグナルベースの状態管理ライブラリです。
1. 状態を定義し、observableにする
状態は、アプリケーションを動かすデータです。通常、todoアイテムのリストのようなドメイン固有の状態と、現在選択されている要素のようなビューの状態があります。状態は、値を保持するスプレッドシートのセルのようなものです。
状態は、プレーンオブジェクト、配列、クラス、循環データ構造、参照など、好きなデータ構造に保存できます。MobXの動作には関係ありません。時間の経過とともに変更したいすべてのプロパティをobservable
としてマークして、MobXがそれらを追跡できるようにしてください。
簡単な例を以下に示します。
import { makeObservable, observable, action } from "mobx"
class Todo {
id = Math.random()
title = ""
finished = false
constructor(title) {
makeObservable(this, {
title: observable,
finished: observable,
toggle: action
})
this.title = title
}
toggle() {
this.finished = !this.finished
}
}
observable
を使用することは、オブジェクトのプロパティをスプレッドシートのセルに変えるようなものです。ただし、スプレッドシートとは異なり、これらの値はプリミティブ値だけでなく、参照、オブジェクト、配列にもなります。
ヒント: クラス、プレーンオブジェクト、デコレータのいずれを優先しますか?MobXはさまざまなスタイルをサポートしています。
この例はmakeAutoObservable
を使用して短縮できますが、明示的にすることで、さまざまな概念をより詳しく紹介できます。MobXはオブジェクトのスタイルを規定していないことに注意してください。代わりにプレーンオブジェクトを使用することも、さらに簡潔なクラスにはデコレータを使用することもできます。詳細については、ページを参照してください。
では、action
としてマークしたtoggle
はどうでしょうか?
2. アクションを使って状態を更新する
アクションとは、状態を変更するコードのことです。ユーザーイベント、バックエンドデータのプッシュ、スケジュールされたイベントなどがあります。アクションは、スプレッドシートのセルに新しい値を入力するユーザーのようなものです。
上記のTodo
モデルでは、finished
の値を変更するtoggle
メソッドがあります。finished
はobservable
としてマークされています。observable
を変更するコードはすべてaction
としてマークすることをお勧めします。そうすることで、MobXはトランザクションを自動的に適用し、手間をかけずに最適なパフォーマンスを実現できます。
アクションを使用すると、コードを構造化し、意図しない場合に状態を誤って変更することを防ぐことができます。状態を変更するメソッドは、MobXの用語ではアクションと呼ばれます。これは、現在の状態に基づいて新しい情報を計算するビューとは対照的です。すべてのメソッドは、それら2つの目標のうち最大1つを満たす必要があります。
3. 状態の変化に自動的に対応する派生を作成する
さらなるやり取りなしに状態から導き出すことができるものは、すべて派生です。派生には、さまざまな形式があります。
- ユーザーインターフェース
- 残りの
todos
の数など、派生データ - サーバーへの変更の送信など、バックエンド統合
MobXは、2種類の派生を区別します。
- 算出値。純粋関数を使用して、現在のobservable状態から常に導出できる値。
- リアクション。状態が変化したときに自動的に発生する必要がある副作用(命令型プログラミングとリアクティブプログラミングの間のブリッジ)。
MobXを使い始めるとき、人々はリアクションを過剰に使用する傾向があります。黄金律は、現在の状態に基づいて値を作成する場合は、常にcomputed
を使用することです。
3.1. computedを使用して派生値をモデル化する
算出値を作成するには、JS getter関数get
を使用してプロパティを定義し、makeObservable
を使用してcomputed
としてマークします。
import { makeObservable, observable, computed } from "mobx"
class TodoList {
todos = []
get unfinishedTodoCount() {
return this.todos.filter(todo => !todo.finished).length
}
constructor(todos) {
makeObservable(this, {
todos: observable,
unfinishedTodoCount: computed
})
this.todos = todos
}
}
MobXは、todoが追加されたとき、またはfinished
プロパティのいずれかが変更されたときに、unfinishedTodoCount
が自動的に更新されるようにします。
これらの計算は、MS Excelのようなスプレッドシートプログラムの数式に似ています。それらは自動的に更新されますが、必要な場合のみです。つまり、何かがその結果に関心を持っている場合です。
3.2. リアクションを使って副作用をモデル化する
ユーザーが画面上で状態や算出値の変化を確認できるようにするには、GUIの一部を再描画するリアクションが必要です。
リアクションは算出値と似ていますが、情報を作成する代わりに、コンソールへの出力、ネットワークリクエストの作成、DOMをパッチするためのReactコンポーネントツリーの段階的な更新などの副作用を作成します。
要するに、リアクションはリアクティブプログラミングと命令型プログラミングの世界を結びつけます。
これまでで最もよく使用されているリアクションの形式は、UIコンポーネントです。アクションとリアクションの両方から副作用をトリガーできることに注意してください。フォームを送信するときにネットワークリクエストを行うなど、トリガーできる明確で明示的な起点を持つ副作用は、関連するイベントハンドラーから明示的にトリガーする必要があります。
3.3. リアクティブなReactコンポーネント
Reactを使用している場合、インストール時に選択したバインディングパッケージのobserver
関数でコンポーネントをラップすることで、コンポーネントをリアクティブにできます。この例では、軽量なmobx-react-lite
パッケージを使用します。
import * as React from "react"
import { render } from "react-dom"
import { observer } from "mobx-react-lite"
const TodoListView = observer(({ todoList }) => (
<div>
<ul>
{todoList.todos.map(todo => (
<TodoView todo={todo} key={todo.id} />
))}
</ul>
Tasks left: {todoList.unfinishedTodoCount}
</div>
))
const TodoView = observer(({ todo }) => (
<li>
<input type="checkbox" checked={todo.finished} onClick={() => todo.toggle()} />
{todo.title}
</li>
))
const store = new TodoList([new Todo("Get Coffee"), new Todo("Write simpler code")])
render(<TodoListView todoList={store} />, document.getElementById("root"))
observer
は、Reactコンポーネントを、レンダリングするデータの派生に変換します。MobXを使用する場合、スマートコンポーネントやダムコンポーネントはありません。すべてのコンポーネントはスマートにレンダリングされますが、ダムな方法で定義されます。MobXは、コンポーネントが必要なときは常に再レンダリングされ、それ以上は決して行わないようにします。
したがって、上記の例のonClick
ハンドラーは、toggle
アクションを使用するため、適切なTodoView
コンポーネントを強制的に再レンダリングしますが、未完了のタスクの数が変更された場合にのみ、TodoListView
コンポーネントを再レンダリングします。また、Tasks left
行を削除した場合(または別のコンポーネントに配置した場合)、タスクをチェックするときにTodoListView
コンポーネントは再レンダリングされなくなります。
ReactがMobXとどのように連携するかについて詳しくは、React統合のセクションをご覧ください。
3.4. カスタムリアクション
これらはまれにしか必要になりませんが、特定の状況に合わせてautorun
、reaction
、またはwhen
関数を使用して作成できます。たとえば、次のautorun
は、unfinishedTodoCount
の量が変更されるたびにログメッセージを出力します。
// A function that automatically observes the state.
autorun(() => {
console.log("Tasks left: " + todos.unfinishedTodoCount)
})
unfinishedTodoCount
が変更されるたびに新しいメッセージが出力されるのはなぜでしょうか?その答えは、次の経験則にあります。
MobXは、追跡された関数の実行中に読み取られる既存のobservableプロパティすべてに反応します。
MobXがどのobservableに反応する必要があるかをどのように判断するかについて詳しくは、「リアクティビティの理解」セクションをご覧ください。
原則
MobXは一方向データフローを使用します。アクションが状態を変更し、それがすべて影響を受けるビューを更新します。
すべての導出は、状態が変更されると自動的かつ原子的に更新されます。その結果、中間値を観測することは決してできません。
すべての導出は、デフォルトで同期的に更新されます。これは、たとえば、アクションが状態を変更した直後に計算された値を安全に検査できることを意味します。
計算値は遅延的に更新されます。アクティブに使用されていない計算値は、副作用(I/O)に必要になるまで更新されません。ビューが使用されなくなると、自動的にガベージコレクションされます。
すべての計算値は純粋である必要があります。それらは状態を変更してはなりません。
背景となるコンテキストの詳細については、MobXの背後にある基本原則をご覧ください。
試してみましょう!
上記の例は、CodeSandboxで自分で試すことができます。
Linting
MobXのメンタルモデルを採用するのが難しい場合は、非常に厳密に構成し、これらのパターンから逸脱するたびにランタイムで警告するように設定してください。「MobXのLinting」セクションをご覧ください。