可観測な状態の作成
プロパティ、オブジェクト全体、配列、マップ、セットはすべて可観測にできます。オブジェクトを可観測にする基本は、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 }
を渡すことができることに注意してください。
利用可能なアノテーション
アノテーション | 説明 |
---|---|
observable observable.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
オブジェクトでラップされていないため、デバッガーでより優れたエクスペリエンスが得られます。最後に、クラスは形状が予測可能で、メソッドがプロトタイプで共有されるため、多くのエンジン最適化の恩恵を受けることができます。ただし、複雑な継承パターンは簡単に落とし穴になる可能性があるため、クラスを使用する場合は、シンプルに保ちます。したがって、クラスを使用することをわずかに推奨する場合でも、それがより適している場合は、このスタイルから逸脱することを強くお勧めします。