mobxで、深い階層のデータをobserveしようと思って、うまくいかないことがあったので調べてみました。
#1. mobx.observeは、深い階層のデータを検知しない
意外かもしれませんが、mobx.observeでは直接的には、深い階層のデータの変更を検知することはできません。
たとえば、以下のようなオブジェクトを考えます。
class Sample {
@observable // @observe.deepと同じ
array = [];
@observable // @observe.deepと同じ
object = {};
}
ここで、以下のようにobserveしても・・・
mobx.observe(sample, "array", change => console.log("observe array"));
mobx.observe(sample, "object", change => console.log("observe object"));
sample.array.push(1);
sample.object.a = 10;
何も表示されません。
observableは、observable.deepと同じはずなのに、
全然deepじゃないじゃん。
そうなんです。
Angulara1.xに存在したwatch deepみたいな挙動を期待してはいけないということです。
#2. でも、検知する方法はある
いや、これでは納得いきませんね。
じゃぁ、observable.deepって何なんだよとなります。
そこで、以下のようにcomputedで、array.slice()で、arrayの中身の全コピーを作ってみます。
また、objectについては、Object.valuesで、全データを取得してみます。
class Sample {
@observable
array = [];
@observable
object = {};
@observable.ref
objectRef = {};
@computed
get _array() {
return this.array.slice();
}
@computed
get _object() {
return Object.values(this.object);
}
@computed
get _objectRef() {
return Object.values(this.objectRef);
}
}
const sample = new Sample();
mobx.autorun(() => {
sample.array;
console.log("autorun array");
});
mobx.autorun(() => {
sample._array;
console.log("autorun _array");
});
mobx.autorun(() => {
sample.object;
console.log("autorun object");
});
mobx.autorun(() => {
sample.objectRef;
console.log("autorun objectRef");
});
mobx.autorun(() => {
sample._object;
console.log("autorun _object");
});
mobx.autorun(() => {
sample._objectRef;
console.log("autorun _objectRef");
});
mobx.observe(sample, "array", change => console.log("observe array"));
mobx.observe(sample, "_array", change => console.log("observe _array"));
mobx.observe(sample, "object", change => console.log("observe object"));
mobx.observe(sample, "objectRef", change => console.log("observe objectRef"));
mobx.observe(sample, "_object", change => console.log("observe _object"));
mobx.observe(sample, "_objectRef", change => console.log("observe _objectRef"));
sample.array.push(1);
// autorun _array
// observe _array
sample.object.a = 1;
// observe _object
// observe _object
sample.objectRef.a = 1;
// 何も表示されない
期待どおりの結果が得られました。
ちなみに、computedを使わず、autorunの中で直接、this.array.slice()や、Object.values(this.object)した場合も同じです。
ただ、observeの場合は、computedと併用するしかなさそうです。
#3. 結論
たしかに、observable.deepは、深い階層まで検知する仕組みを提供しています。
ただ、observeが、それに対応してないだけです。
autorunとかcomputedで、明示的に深い階層を参照してやれば、深い階層の変更を検知することができます。
さて、上の例では、this.array.slice()や、Object.values(this.object)のような重い操作をして実験しましたが、
通常はこういう操作をする必要はないはずで、
mobxにまかせてしまえば、最適なタイミングで更新処理がかかるはずです。
たとえば、this.array[3]と書けば、配列の3番目が更新されたときだけ、それに依存する処理が走るわけです。
基本的な考え方としては、
- observeは、深い階層の変更を検知しない
- observeで、深い階層の検知をしたくなったら、そもそも設計が間違っている可能性があるので、設計を見直そう。
という話ではないかと思います。
個人的には、observe.deepみたいな機能があっても良いのではないかとは思うのですが・・・。