可観測な状態の作成
プロパティ、オブジェクト全体、配列、マップ、セットはすべて可観測にできます。オブジェクトを可観測にする基本は、makeObservableを使用してプロパティごとにアノテーションを指定することです。最も重要なアノテーションは次のとおりです。
observableは、状態を格納する追跡可能なフィールドを定義します。actionは、状態を修正するアクションとしてメソッドをマークします。computedは、状態から新しい事実を導き出し、その出力をキャッシュするゲッターをマークします。
makeObservable
使用法
makeObservable(target, annotations?, options?)
この関数は、既存のオブジェクトプロパティを可観測にするために使用できます。JavaScriptオブジェクト(クラスインスタンスを含む)はすべてtargetに渡すことができます。通常、makeObservableはクラスのコンストラクターで使用され、その最初の引数はthisです。annotations引数は、アノテーションを各メンバーにマッピングします。アノテーション付きのメンバーのみが影響を受けます。
または、コンストラクターでmakeObservableを呼び出す代わりに、クラスメンバーで@observableのようなデコレーターを使用できます。
情報を導出し、引数を受け取るメソッド(たとえば、findUsersOlderThan(age: number): User[])は、computedとしてアノテーションを付けることはできません。それらの読み取り操作は、リアクションから呼び出されたときに追跡されますが、メモリリークを避けるために出力はメモ化されません。このようなメソッドをメモ化するには、MobX-utils computedFn {🚀}を使用できます。
サブクラス化は、overrideアノテーションを使用することで、いくつかの制限付きでサポートされています(例はこちらを参照)。
import { makeObservable, observable, computed, action, flow } from "mobx"
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
increment: action,
fetch: flow
})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
}
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
すべてのアノテーション付きフィールドは構成可能ではありません。
すべての非可観測(ステートレス)フィールド(action、flow)は書き込み可能ではありません。
最新のデコレーターを使用する場合、makeObservableを呼び出す必要はありません。以下は、デコレーターベースのクラスがどのように見えるかを示しています。@observableアノテーションは常にaccessorキーワードと組み合わせて使用する必要があることに注意してください。
import { observable, computed, action, flow } from "mobx"
class Doubler {
@observable accessor value
constructor(value) {
this.value = value
}
@computed
get double() {
return this.value * 2
}
@action
increment() {
this.value++
}
@flow
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
import { makeAutoObservable } from "mobx"
function createDoubler(value) {
return makeAutoObservable({
value,
get double() {
return this.value * 2
},
increment() {
this.value++
}
})
}
クラスはmakeAutoObservableも活用できることに注意してください。例の違いは、MobXをさまざまなプログラミングスタイルに適用する方法を示すだけです。
import { observable } from "mobx"
const todosById = observable({
"TODO-123": {
title: "find a decent task management system",
done: false
}
})
todosById["TODO-456"] = {
title: "close all tickets older than two weeks",
done: true
}
const tags = observable(["high prio", "medium prio", "low prio"])
tags.push("prio: for fun")
makeObservableを使用した最初の例とは対照的に、observableはオブジェクトへのフィールドの追加(および削除)をサポートします。これにより、observableは、動的にキーが設定されたオブジェクト、配列、マップ、セットなどのコレクションに最適です。
レガシーデコレーターを使用するには、デコレーターが機能するように、コンストラクターでmakeObservable(this)を呼び出す必要があります。
import { observable, computed, action, flow } from "mobx"
class Doubler {
@observable value
constructor(value) {
makeObservable(this)
this.value = value
}
@computed
get double() {
return this.value * 2
}
@action
increment() {
this.value++
}
@flow
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
makeAutoObservable
使用法
makeAutoObservable(target, overrides?, options?)
makeAutoObservableは、すべてのプロパティをデフォルトで推論するため、ステロイド上のmakeObservableのようなものです。ただし、overridesパラメータを使用して、特定のアノテーションでデフォルトの動作をオーバーライドできます。特にfalseは、プロパティまたはメソッドを完全に処理から除外するために使用できます。上記のコードを例として確認してください。
makeAutoObservable関数は、新しいメンバーを明示的に記述する必要がないため、makeObservableを使用するよりもコンパクトで保守が容易です。ただし、makeAutoObservableは、スーパークラスを持つクラスや、サブクラス化されたクラスでは使用できません。
推論ルール
- すべての独自のプロパティは
observableになります。 - すべての
gettersはcomputedになります。 - すべての
settersはactionになります。 - すべての関数は
autoActionになります。 - すべてのジェネレーター関数は
flowになります。(ジェネレーター関数は、一部のトランスパイラー構成では検出できないことに注意してください。フローが期待どおりに機能しない場合は、必ずflowを明示的に指定してください。) overrides引数でfalseとマークされたメンバーは、アノテーションが付けられません。たとえば、識別子などの読み取り専用フィールドに使用します。
observable
使用法
observable(source, overrides?, options?)@observable accessor(フィールドデコレーター)
observableアノテーションは、関数として呼び出して、オブジェクト全体を一度に可観測にすることもできます。sourceオブジェクトは複製され、すべてのメンバーはmakeAutoObservableで行うのと同様に可観測になります。同様に、overridesマップを提供して、特定のメンバーのアノテーションを指定できます。上記のコードブロックの例を確認してください。
observableによって返されるオブジェクトはProxyになります。つまり、オブジェクトに後で追加されたプロパティも、可観測になり(プロキシの使用が無効になっている場合を除く)。
observableメソッドは、配列、マップ、セットなどのコレクション型で呼び出すこともできます。それらも複製され、可観測な対応物に変換されます。
例:可観測配列
次の例では、可観測を作成し、autorunを使用してそれを監視します。マップとセットコレクションの操作も同様に機能します。
import { observable, autorun } from "mobx"
const todos = observable([
{ title: "Spoil tea", completed: true },
{ title: "Make coffee", completed: false }
])
autorun(() => {
console.log(
"Remaining:",
todos
.filter(todo => !todo.completed)
.map(todo => todo.title)
.join(", ")
)
})
// Prints: 'Remaining: Make coffee'
todos[0].completed = false
// Prints: 'Remaining: Spoil tea, Make coffee'
todos[2] = { title: "Take a nap", completed: false }
// Prints: 'Remaining: Spoil tea, Make coffee, Take a nap'
todos.shift()
// Prints: 'Remaining: Make coffee, Take a nap'
可観測配列には、さらに便利なユーティリティ関数がいくつかあります
clear()配列から現在のすべてのエントリを削除します。replace(newItems)配列内の既存のすべてのエントリを新しいエントリに置き換えます。remove(value)値によって配列から単一のアイテムを削除します。アイテムが見つかって削除された場合はtrueを返します。
注:プリミティブとクラスインスタンスは可観測に変換されません
プリミティブ値はJavaScriptでは不変であるため、MobXで可観測にすることはできません(ただし、ボックス化できます)。ただし、通常、このメカニズムはライブラリ以外では使用されません。
クラスインスタンスは、observableに渡したり、observableプロパティに割り当てたりしても、自動的に可観測になることはありません。クラスメンバーを可観測にすることは、クラスコンストラクターの責任と見なされます。
{🚀} ヒント:可観測(プロキシ)とmakeObservable(非プロキシ)
make(Auto)Observableとobservableの主な違いは、最初の引数として渡すオブジェクトを前者では変更するのに対し、observableでは可観測にされたクローンを作成することです。
2番目の違いは、observableはProxyオブジェクトを作成することです。これにより、動的ルックアップマップとしてオブジェクトを使用する場合に、将来のプロパティの追加をトラップできます。可観測にしたいオブジェクトにすべてのメンバーが事前に既知の規則的な構造がある場合は、プロキシを使用しないオブジェクトの方が少し高速であり、デバッガーやconsole.logで検査するのが容易なため、makeObservableを使用することをお勧めします。
そのため、make(Auto)Observableはファクトリ関数で使用することをお勧めするAPIです。非プロキシクローンを取得するために、observableにオプションとして{ proxy: false }を渡すことができることに注意してください。
利用可能なアノテーション
| アノテーション | 説明 |
|---|---|
observableobservable.deep | 状態を格納する追跡可能なフィールドを定義します。可能な場合、observableに代入される値は、その型に基づいて自動的に(ディープな)observable、autoAction、またはflowに変換されます。変換可能なのはplain object、array、Map、Set、function、generator functionのみです。クラスのインスタンスなどは変更されません。 |
observable.ref | observableと同様ですが、再代入のみが追跡されます。代入された値は完全に無視され、自動的にobservable/ autoAction / flowに変換されることはありません。たとえば、不変データをobservableフィールドに格納する場合に使用します。 |
observable.shallow | observable.refと同様ですが、コレクション用です。代入されたコレクションはobservableになりますが、コレクションの内容自体はobservableになりません。 |
observable.struct | observableと同様ですが、代入された値が現在の値と構造的に等しい場合は無視されます。 |
action | 状態を修正するアクションとしてメソッドをマークします。詳細についてはactionsをご覧ください。書き込み不可です。 |
action.bound | actionと同様ですが、アクションをインスタンスにバインドしてthisが常に設定されるようにします。書き込み不可です。 |
computed | getterで使用して、キャッシュ可能な派生値として宣言できます。詳細についてはcomputedsをご覧ください。 |
computed.struct | computedと同様ですが、再計算後の結果が以前の結果と構造的に等しい場合、オブザーバーには通知されません。 |
true | 最適なアノテーションを推論します。詳細についてはmakeAutoObservableをご覧ください。 |
false | このプロパティを明示的にアノテーションしないようにします。 |
flow | 非同期プロセスを管理するためのflowを作成します。詳細についてはflowをご覧ください。TypeScriptでの推論される戻り値の型が正しくない可能性があることに注意してください。書き込み不可です。 |
flow.bound | flowと同様ですが、flowをインスタンスにバインドしてthisが常に設定されるようにします。書き込み不可です。 |
override | サブクラスによってオーバーライドされた、継承されたaction、flow、computed、action.boundに適用可能です。. |
autoAction | 明示的に使用すべきではありませんが、makeAutoObservableによって、呼び出しコンテキストに基づいてアクションまたは派生として機能できるメソッドをマークするために内部で使用されます。関数が派生であるかアクションであるかは、実行時に決定されます。 |
制限事項
make(Auto)Observableは、すでに定義されているプロパティのみをサポートしています。コンパイラ構成が正しいこと、または回避策として、make(Auto)Observableを使用する前にすべてのプロパティに値を割り当てるようにしてください。正しい構成がない場合、宣言はされているが初期化されていないフィールド(class X { y; }など)は正しく認識されません。makeObservableは、独自のクラス定義によって宣言されたプロパティのみをアノテーションできます。サブクラスまたはスーパークラスがobservableフィールドを導入する場合、それらのプロパティに対して自身でmakeObservableを呼び出す必要があります。options引数は1回だけ指定できます。渡されたoptionsは"固定"されており、後で変更することはできません(例:サブクラス)。- すべてのフィールドは1回のみアノテーションできます(
overrideを除く)。フィールドのアノテーションまたは構成は、サブクラスで変更できません。 - 非プレーンオブジェクト(クラス)のすべてのアノテーション付きフィールドは構成不可能です。
configure({ safeDescriptors: false })で無効にできます {🚀☣️}. - すべての非可観測(ステートレス)フィールド(
action、flow)は書き込み可能ではありません。
configure({ safeDescriptors: false })で無効にできます {🚀☣️}. - プロトタイプで定義された
action、computed、flow、action.boundのみが、サブクラスによってオーバーライドできます。. - デフォルトでは、TypeScriptはprivateフィールドのアノテーションを許可しません。これは、次のように関連するprivateフィールドをジェネリック引数として明示的に渡すことで克服できます:
makeObservable<MyStore, "privateField" | "privateField2">(this, { privateField: observable, privateField2: observable }) make(Auto)Observableを呼び出し、アノテーションを提供することは、推論結果をキャッシュできるようにするために、無条件に行う必要があります。make(Auto)Observableが呼び出された後にプロトタイプを変更することはサポートされていません。- EcmaScriptのprivateフィールド(
#field)はサポートされていません。TypeScriptを使用する場合は、代わりにprivate修飾子を使用することをお勧めします。 - 単一の継承チェーン内でアノテーションとデコレーターを混在させることはサポートされていません。たとえば、スーパークラスにはデコレーターを使用し、サブクラスにはアノテーションを使用することはできません。
makeObservable,extendObservableは、他の組み込みのobservable型(ObservableMap、ObservableSet、ObservableArrayなど)では使用できません。makeObservable(Object.create(prototype))は、prototypeから作成されたオブジェクトにプロパティをコピーし、それらをobservableにします。この動作は誤りで予期しないため、非推奨であり、将来のバージョンで変更される可能性があります。これに依存しないでください。
オプション {🚀}
上記APIは、以下のオプションをサポートするオプションのoptions引数を受け取ります
autoBind: trueは、action/flowの代わりに、デフォルトでaction.bound/flow.boundを使用します。明示的にアノテーションされたメンバーには影響しません。deep: falseは、observableの代わりに、デフォルトでobservable.refを使用します。明示的にアノテーションされたメンバーには影響しません。name: <string>は、エラーメッセージやリフレクションAPIに表示されるデバッグ名をオブジェクトに付与します。proxy: falseは、observable(thing)に非プロキシ実装を使用することを強制します。これは、オブジェクトの形状が時間とともに変化しない場合に適したオプションです。プロキシを使用しないオブジェクトはデバッグが容易で高速だからです。このオプションは、make(Auto)Observableでは利用できません。 プロキシの回避を参照してください。
注意:オプションは固定されており、1回だけ指定できます
options引数は、まだobservableではないtargetに対してのみ提供できます。observableオブジェクトが初期化されると、オプションを変更することはできません。
オプションはターゲットに保存され、後続の
makeObservable/extendObservable呼び出しで尊重されます。サブクラスで異なるオプションを渡すことはできません。
observableをプレーンなJavaScriptコレクションに変換する
observableデータ構造をプレーンな対応物に戻す必要がある場合があります。たとえば、observableを追跡できないReactコンポーネントにobservableオブジェクトを渡す場合や、それ以上変更すべきではないクローンを取得する場合などです。
コレクションを浅く変換するには、通常のJavaScriptメカニズムが機能します
const plainObject = { ...observableObject }
const plainArray = observableArray.slice()
const plainMap = new Map(observableMap)
データツリーを再帰的にプレーンオブジェクトに変換するには、toJSユーティリティを使用できます。クラスの場合は、JSON.stringifyによって認識されるため、toJSON()メソッドを実装することをお勧めします。
クラスに関する簡単な注記
これまでのほとんどの例は、クラス構文に偏っていました。原則として、MobXはこれについて独自の見解を持っておらず、プレーンオブジェクトを使用するMobXユーザーも同じくらい多くいると思われます。ただし、クラスのわずかな利点は、TypeScriptなどのAPIをより簡単に見つけることができることです。また、instanceofチェックは型推論に非常に強力であり、クラスインスタンスはProxyオブジェクトでラップされていないため、デバッガーでより優れたエクスペリエンスが得られます。最後に、クラスは形状が予測可能で、メソッドがプロトタイプで共有されるため、多くのエンジン最適化の恩恵を受けることができます。ただし、複雑な継承パターンは簡単に落とし穴になる可能性があるため、クラスを使用する場合は、シンプルに保ちます。したがって、クラスを使用することをわずかに推奨する場合でも、それがより適している場合は、このスタイルから逸脱することを強くお勧めします。
