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
これにより、何も観測していない場合に計算値の一時停止を回避します(上記の解説を参照)。リアクションで説明されているものと同様のメモリリークが発生する可能性があります。