設定 {🚀}
MobXには、好みの使い方、対象とするJavaScriptエンジン、MobXにベストプラクティスを促したいかどうかによって、いくつかの設定があります。ほとんどの設定オプションは、configure
メソッドを使用して設定できます。
プロキシサポート
デフォルトでは、MobXはプロキシを使用して配列とプレーンオブジェクトを監視可能にします。プロキシは、環境全体で最高のパフォーマンスと最も一貫性のある動作を提供します。ただし、プロキシをサポートしない環境をターゲットにしている場合は、プロキシサポートを無効にする必要があります。特にこれは、Internet ExplorerやHermesエンジンを使用していないReact Nativeをターゲットにしている場合に当てはまります。
プロキシサポートは、configure
を使用して無効にできます。
import { configure } from "mobx"
configure({
useProxies: "never"
})
useProxies
設定に受け入れられる値は次のとおりです。
"always"
(デフォルト):MobXは、Proxy
サポートのある環境でのみ実行されることを想定しており、そのような環境が利用できない場合はエラーが発生します。"never"
:プロキシは使用されず、MobXはプロキシ以外の代替手段にフォールバックします。これはすべてのES5環境と互換性がありますが、さまざまな制限が発生します。"ifavailable"
(実験的):プロキシが利用可能な場合はプロキシが使用され、それ以外の場合はMobXがプロキシ以外の代替手段にフォールバックします。このモードの利点は、ES5環境では機能しないAPIまたは言語機能が使用されている場合に、MobXが警告を試み、最新の環境でES5の制限に遭遇するとエラーが発生することです。
注: MobX 6より前は、古いエンジンにはMobX 4、新しいエンジンにはMobX 5のいずれかを選択する必要がありました。ただし、MobX 6は両方をサポートしていますが、古いJavaScriptエンジンをターゲットにする場合は、Mapのような特定のAPIのポリフィルが必要になります。プロキシはポリフィルできません。ポリフィルは存在しますが、完全な仕様をサポートしておらず、MobXには不適切です。使用しないでください。
プロキシサポートがない場合の制限
- 監視可能な配列は実際の配列ではないため、
Array.isArray()
チェックに合格しません。実際の結果として、サードパーティのライブラリに渡す前に、最初に配列を.slice()
(実際の配列の浅いコピーを取得するため)する必要があることがよくあります。たとえば、監視可能な配列を連結しても期待どおりに機能しないため、最初に.slice()
します。 - 作成後に既存の監視可能なプレーンオブジェクトのプロパティを追加または削除しても、自動的に取得されません。オブジェクトをインデックスベースのルックアップマップとして、つまり動的なもののコレクションとして使用する場合は、代わりに監視可能なMapを使用してください。
プロキシが有効になっていなくても、オブジェクトにプロパティを動的に追加し、その追加を検出することは可能です。これは、コレクションユーティリティ {🚀}を使用することで実現できます。(新しい)プロパティがset
ユーティリティを使用して設定されていること、およびオブジェクトが組み込みのJavaScriptメカニズムではなく、values
/ keys
またはentries
ユーティリティのいずれかを使用して反復処理されることを確認してください。ただし、これは本当に忘れやすいため、可能な場合は監視可能なMapを使用することをお勧めします。
デコレーターサポート
実験的なデコレーターサポートを有効にするには、デコレーターの有効化 {🚀}セクションを確認してください。
リンティングオプション
MobXが提唱するパターン、つまりアクション、状態、派生を厳密に分離することを支援するために、MobXは、臭いを示唆することで、実行時にコーディングパターンを「リンティング」できます。MobXを可能な限り厳密にするには、次の設定を採用し、その説明を読み進めてください。
import { configure } from "mobx"
configure({
enforceActions: "always",
computedRequiresReaction: true,
reactionRequiresObservable: true,
observableRequiresReaction: true,
disableErrorBoundaries: true
})
ある時点で、このレベルの厳密さが非常に煩わしいことに気づくでしょう。あなた(と同僚)がMobXのメンタルモデルを理解したら、生産性を向上させるためにこれらのルールを無効にしてもかまいません。
また、これらのルールによってトリガーされた警告を抑制する必要がある場合もあります(たとえば、runInAction
でラップするなど)。それは大丈夫です、これらの推奨事項には良い例外があります。それらについて原理主義的にならないでください。
eslint
プラグインも試してみてください。一部の問題は静的に発見できますが、他の問題は実行時にのみ検出できます。このプラグインは、これらのルールを置き換えるのではなく、補完することを目的としています。自動修正機能は、ボイラープレートコードにも役立ちます。
enforceActions
enforceActionsの目的は、イベントハンドラーをaction
でラップすることを忘れないようにすることです。
可能なオプション
"observed"
(デフォルト):どこかで監視されるすべての状態は、アクションを通じて変更する必要があります。これはデフォルトであり、重要でないアプリケーションで推奨される厳密モードです。"never"
:状態はどこからでも変更できます。"always"
:状態は常にアクションを通じて変更する必要があります。実際には、作成も含まれます。
"observed"
の利点は、アクションの外部で監視可能オブジェクトを作成し、まだどこでも使用されていない限り、自由にそれらを変更できることです。
原則として、状態は常にいくつかのイベントハンドラーから作成する必要があり、イベントハンドラーはラップする必要があるため、"always"
はこれを最もよく捉えています。ただし、おそらく単体テストではこのモードを使用したくありません。
監視可能オブジェクトを遅延的に作成する場合、たとえば算出プロパティでは、runInAction
を使用して作成をアドホックでアクションでラップできます。
computedRequiresReaction: boolean
アクションまたはリアクションの外部から監視されていない算出値に直接アクセスすることを禁止します。これにより、MobXがそれらをキャッシュしない方法で算出値を使用していないことが保証されます。デフォルト:false
。
次の例では、MobXは最初のコードブロックで算出値をキャッシュしませんが、2番目と3番目のブロックで結果をキャッシュします。
class Clock {
seconds = 0
get milliseconds() {
console.log("computing")
return this.seconds * 1000
}
constructor() {
makeAutoObservable(this)
}
}
const clock = new Clock()
{
// This would compute twice, but is warned against by this flag.
console.log(clock.milliseconds)
console.log(clock.milliseconds)
}
{
runInAction(() => {
// Will compute only once.
console.log(clock.milliseconds)
console.log(clock.milliseconds)
})
}
{
autorun(() => {
// Will compute only once.
console.log(clock.milliseconds)
console.log(clock.milliseconds)
})
}
observableRequiresReaction: boolean
監視されていない observable へのアクセスについて警告します。「MobX コンテキスト」なしで observable を使用しているかどうかを確認したい場合にこれを使用します。これは、例えば React コンポーネントで observer
ラッパーが不足している箇所を見つけるのに非常に役立ちます。ただし、アクションの欠落も見つけます。デフォルト: false
configure({ observableRequiresReaction: true })
注意: observer
でラップされたコンポーネントで propTypes を使用すると、このルールで誤検出が発生する可能性があります。
reactionRequiresObservable: boolean
observable にアクセスせずにリアクション (例: autorun
) が作成された場合に警告します。これにより、React コンポーネントを不必要に observer
でラップしたり、関数を action
でラップしたり、単にいくつかのデータ構造やプロパティを observable にすることを忘れたりした場合をチェックできます。デフォルト: false
configure({ reactionRequiresObservable: true })
disableErrorBoundaries: boolean
デフォルトでは、MobX はコード内で発生した例外をキャッチして再スローします。これは、1つの例外でのリアクションが、他の関連性のない可能性のあるリアクションのスケジュールされた実行を妨げないようにするためです。つまり、例外は元の原因コードに伝播されないため、try/catch を使用してキャッチすることはできません。
エラー境界を無効にすると、例外が派生からエスケープできます。これによりデバッグが容易になる可能性がありますが、MobX および拡張機能によってアプリケーションが回復不能な破損状態になる可能性があります。デフォルト: false
。
このオプションは単体テストに最適ですが、例えば jest で afterEach
を使用するなどして、各テストの後に _resetGlobalState
を呼び出すことを忘れないでください。
import { _resetGlobalState, observable, autorun, configure } from "mobx"
configure({ disableErrorBoundaries: true })
test("Throw if age is negative", () => {
expect(() => {
const age = observable.box(10)
autorun(() => {
if (age.get() < 0) throw new Error("Age should not be negative")
})
age.set(-1)
}).toThrow("Age should not be negative")
})
afterEach(() => {
_resetGlobalState()
})
safeDescriptors: boolean
MobX は、サポートされていないことや、コードを破損させる可能性のあることを防ぐために、いくつかのフィールドを設定不可または書き込み不可にします。ただし、これによりテストでのスパイ/モック/スタブも妨げられる可能性があります。configure({ safeDescriptors: false })
は、この安全対策を無効にし、すべてを設定可能および書き込み可能にします。既存の observable には影響せず、構成後に作成された observable のみに影響することに注意してください。注意して使用してください。必要な場合にのみ使用し、すべてのテストでグローバルにオフにしないでください。そうしないと、偽陽性(破損したコードでテストに合格する)のリスクがあります。デフォルト: true
configure({ safeDescriptors: false })
その他の構成オプション
isolateGlobalState: boolean
同じ環境内で複数の MobX インスタンスがアクティブな場合に、MobX のグローバル状態を分離します。これは、MobX を使用するカプセル化されたライブラリが、MobX を使用するアプリと同じページに存在する場合に役立ちます。ライブラリから configure({ isolateGlobalState: true })
を呼び出すと、ライブラリ内のリアクティビティは自己完結型になります。
このオプションがない場合、複数の MobX インスタンスがアクティブであると、内部状態が共有されます。利点は、両方のインスタンスからの observable が連携して動作することですが、欠点は MobX のバージョンが一致する必要があることです。デフォルト: false
configure({ isolateGlobalState: true })
reactionScheduler: (f: () => void) => void
すべての MobX リアクションを実行する新しい関数を設定します。デフォルトでは、reactionScheduler
は他の動作なしに f
リアクションを実行するだけです。これは、基本的なデバッグや、アプリケーションの更新を視覚化するためにリアクションを遅くする場合に役立ちます。デフォルト: f => f()
configure({
reactionScheduler: (f): void => {
console.log("Running an event after a delay:", f)
setTimeout(f, 100)
}
})