computedによる情報の導出
使用方法
computed(アノテーション)computed(options)(アノテーション)computed(fn, options?)@computed(ゲッターデコレータ)@computed(options)(ゲッターデコレータ)
computed値は、他のオブザーバブルから情報を導出するために使用できます。それらは遅延評価され、出力をキャッシュし、基礎となるオブザーバブルのいずれかが変更された場合にのみ再計算されます。何もオブザーブされていない場合、完全に中断されます。
概念的には、スプレッドシートの式と非常に似ており、過小評価することはできません。これらは、保存する必要がある状態の量を削減し、高度に最適化されています。可能な限り使用してください。
例
computed値は、JavaScriptのゲッターをcomputedでアノテーションすることで作成できます。ゲッターをcomputedとして宣言するには、makeObservableを使用します。代わりにすべてのゲッターを自動的にcomputedとして宣言したい場合は、makeAutoObservable、observable、またはextendObservableを使用できます。computedゲッターは非列挙になります。
computed値のポイントを説明するために、以下の例は、autorun(高度なセクションのリアクション {🚀}を参照)を使用しています。
import { makeObservable, observable, computed, autorun } from "mobx"
class OrderLine {
price = 0
amount = 1
constructor(price) {
makeObservable(this, {
price: observable,
amount: observable,
total: computed
})
this.price = price
}
get total() {
console.log("Computing...")
return this.price * this.amount
}
}
const order = new OrderLine(0)
const stop = autorun(() => {
console.log("Total: " + order.total)
})
// Computing...
// Total: 0
console.log(order.total)
// (No recomputing!)
// 0
order.amount = 5
// Computing...
// (No autorun)
order.price = 2
// Computing...
// Total: 10
stop()
order.price = 3
// Neither the computation nor autorun will be recomputed.
上記の例は、computed値の利点をうまく示しています。これはキャッシュポイントとして機能します。amountを変更しても、これによりtotalが再計算されますが、totalは出力が影響を受けていないことを検出するため、autorunはトリガーされません。そのため、autorunを更新する必要はありません。
これに対し、totalにアノテーションが付けられていない場合、autorunはその効果を3回実行します。これは、totalとamountに直接依存するためです。自分で試してみてください。

これは、上記の例で作成される依存関係グラフです。
ルール
computed値を使用する際には、いくつかのベストプラクティスに従う必要があります。
- 副作用があってはいけません。また、他のオブザーバブルを更新してはいけません。
- 新しいオブザーバブルを作成して返すのを避けてください。
- オブザーバブルでない値に依存してはいけません。
ヒント
ヒント:computed値は、オブザーブされていない場合、中断されます
MobXに慣れていない人、例えばReselectのようなライブラリに慣れている人は、computedプロパティを作成してもリアクションでどこにも使用しない場合、メモ化されず、必要以上に頻繁に再計算されるように見えるため、混乱することがあります。例えば、上記の例をconsole.log(order.total)を2回呼び出した後、stop()を呼び出した後に拡張した場合、値は2回再計算されます。
これにより、MobXは、アクセスされていないcomputed値への不要な更新を回避するために、現在アクティブに使用されていない計算を自動的に中断できます。しかし、computedプロパティが何らかのリアクションによって使用されていない場合、computed式は値が要求されるたびに評価されるため、通常のプロパティのように動作します。
computedプロパティだけをいじくり回すと効率が悪いように見えるかもしれませんが、observer、autorunなどを使用するプロジェクトに適用すると、非常に効率的になります。
以下のコードは、この問題を示しています。
// OrderLine has a computed property `total`.
const line = new OrderLine(2.0)
// If you access `line.total` outside of a reaction, it is recomputed every time.
setInterval(() => {
console.log(line.total)
}, 60)
これは、keepAliveオプション付きのアノテーションを設定することで上書きできます(自分で試してみてください)。または、後で必要に応じてきれいに整理できる無効なautorun(() => { someObject.someComputed })を作成することで上書きできます。どちらの方法にもメモリリークが発生するリスクがあることに注意してください。ここのデフォルトの動作を変更することは、アンチパターンです。
MobXは、computedRequiresReactionオプションを使用して、リアクティブなコンテキストの外でcomputedがアクセスされた場合にエラーを報告するように設定することもできます。
ヒント:computed値にはセッターを設定できます
computed値にもセッターを定義できます。これらのセッターは、computedプロパティの値を直接変更するために使用することはできませんが、導出の「逆」として使用できます。セッターは自動的にアクションとしてマークされます。例えば
class Dimension {
length = 2
constructor() {
makeAutoObservable(this)
}
get squared() {
return this.length * this.length
}
set squared(value) {
this.length = Math.sqrt(value)
}
}
{🚀} ヒント:出力を構造的に比較するためのcomputed.struct
前の計算と構造的に等価なcomputed値の出力がオブザーバーに通知する必要がない場合、computed.structを使用できます。これにより、オブザーバーに通知する前に、参照の等価性チェックではなく、構造比較が最初に実行されます。例えば
class Box {
width = 0
height = 0
constructor() {
makeObservable(this, {
width: observable,
height: observable,
topRight: computed.struct
})
}
get topRight() {
return {
x: this.width,
y: this.height
}
}
}
デフォルトでは、computedの出力が参照によって比較されます。上記の例では、topRightは常に新しい結果オブジェクトを生成するため、以前の出力と等しいと見なされることはありません。computed.structを使用しない限り。
しかし、上記の例では、実際にはcomputed.structは必要ありません!computed値は、通常、バッキング値が変更された場合にのみ再評価されます。そのため、topRightはwidthまたはheightの変更にのみ反応します。これらのいずれかが変更されると、いずれにしても異なるtopRight座標が得られるためです。computed.structはキャッシュヒットがなく、無駄な労力となるため、必要ありません。
実際には、computed.structは思っているほど役に立ちません。基礎となるオブザーバブルの変更でも同じ出力が得られる場合にのみ使用してください。例えば、座標を最初に丸めていた場合、基礎となる値が異なっていても、丸めた座標は以前の丸めた座標と等しい場合があります。
equalsオプションを参照して、出力が変更されたかどうかを判断する方法をさらにカスタマイズしてください。
{🚀} ヒント:引数付きcomputed値
ゲッターは引数を取りませんが、引数が必要な導出値を操作するためのいくつかの戦略がこちらで説明されています。
{🚀} ヒント:computed(expression)を使用してスタンドアロンのcomputed値を作成する
computedは、observable.boxがスタンドアロンのcomputed値を作成するのと同じように、関数として直接呼び出すこともできます。返されたオブジェクトで.get()を使用して、計算の現在の値を取得します。この形式のcomputedはあまり使用されませんが、「ボックス化された」computed値を渡す必要がある場合に役立つ場合があります。その1つの例がこちらで説明されています。
オプション {🚀}
computedは通常、すぐに思い通りの動作をします。しかし、options引数を渡すことで動作をカスタマイズできます。
name
この文字列は、SpyイベントリスナーとMobX開発者ツールでのデバッグ名として使用されます。
equals
デフォルトでcomparer.defaultに設定されています。これは、前の値と次の値を比較するための比較関数として機能します。この関数が値を等しいとみなした場合、オブザーバーは再評価されません。
これは、構造化データや他のライブラリの型を扱う際に役立ちます。たとえば、計算されたmomentインスタンスでは(a, b) => a.isSame(b)を使用できます。構造比較/浅い比較を使用して新しい値が前の値と異なるかどうかを判断し、その結果、オブザーバーに通知したい場合は、comparer.structuralとcomparer.shallowが役立ちます。
上記のcomputed.structセクションをご覧ください。
組み込み比較関数
MobXは、computedのequalsオプションのほとんどのニーズを満たす4つの組み込みcomparerメソッドを提供します。
comparer.identityは、同一性(===)演算子を使用して2つの値が同じかどうかを判断します。comparer.defaultはcomparer.identityと同じですが、NaNをNaNと等しいとみなします。comparer.structuralは、深層構造比較を実行して、2つの値が同じかどうかを判断します。comparer.shallowは、浅い構造比較を実行して、2つの値が同じかどうかを判断します。
これらのメソッドにアクセスするには、mobxからcomparerをインポートできます。これらはreactionにも使用できます。
requiresReaction
非常に高価な計算値には、これをtrueに設定することをお勧めします。リアクティブコンテキストの外でその値を読み取ろうとすると、キャッシュされていない可能性があり、高価な再評価を行う代わりに、計算値がエラーをスローします。
keepAlive
これにより、何も観測していない場合に計算値の一時停止を回避します(上記の解説を参照)。リアクションで説明されているものと同様のメモリリークが発生する可能性があります。
