3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

mobxで深い階層の変更を検知する

Posted at

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番目が更新されたときだけ、それに依存する処理が走るわけです。

基本的な考え方としては、

  1. observeは、深い階層の変更を検知しない
  2. observeで、深い階層の検知をしたくなったら、そもそも設計が間違っている可能性があるので、設計を見直そう。
    という話ではないかと思います。

個人的には、observe.deepみたいな機能があっても良いのではないかとは思うのですが・・・。

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?