🧊

0からはじめる MobX Part.3

GitHub - mobxjs/mobx: Simple, scalable state management.

MobXの普及活動、Part.3です。

でもそもそもそんなに書くことないのでそろそろ終わってしまいそう・・!

ObseravableArray

MobXでObservableにした値は、いちおう元の型っぽく振る舞うけど微妙に違う。
その代表例がコイツ。

const arr = observable([]);

Array.isArray(arr); // false

こういうの。

そして、Array.prototypeのメソッドはぜんぶ使えるけど、加えて以下も追加されてる。

  • `clear()`: その名の通り、空っぽにする
  • `replace()`: 渡した配列でまるっと置き換え
  • `find()`: Array.findと一緒
  • `remove()`: 渡したvalueを見つけて削除するやつ
  • `peek()`: `slice()`と基本的には同じだが、以降の配列操作が認められなくなる

`peek()`について補足すると、

const x = y = mobx.observable([1, 2, 3]);

x.peek().push(4);
x.push(4); // throw error

y.slice().push(4);
y.push(4); // 問題ない

というわけで、

  • `slice()`がコピーを返すのに対して、`peek()`はMobXが内部的に保持してる参照をそのまま返す(速度的に有利)
  • そうして得た配列を変更しちゃうと、辻褄を保証できなくなってエラーになる
  • なので使う時は注意して、パフォーマンスに困ってないなら`slice()`でだいたいなんとかなる


まぁいわゆるObservable系なライブラリとしてはお馴染みなインターフェースかなーと。
もちろん`push()`とか`pop()`とかおなじみのメソッドでデータを更新しても拾ってくれます。

`Array.isArray()`したい派の人のために、v2.6.2から`isArrayLike()`ってメソッドが追加されました。

ObservableMap

MobXの最初のつまづきポイントであるオブジェクトに途中からキーを追加するやつ。

// 渡されたオブジェクトをただ表示するマン
const Foo = observer(({ data }) => {
  return (
    <div>{JSON.stringify(data)}</div>
  );
});

// でも最初が空だと
const data = observable({});

// あれ?反映されない?
data1.foo = Date.now();

`data`を宣言してる時点で存在しないキーは、後から追加しても反応してくれません。(他のキーの更新に紛れて反応してるように見えたりはする)

まぁそういう時に使うのがObservableMap。
ふつうにオブジェクトを渡したときはObservableObjectといって別物なのでご注意。

// const data = observable({});
// ではなく
const data = observable(asMap({}));

// 反映される!
data.set('foo', Date.now());

ES6のMapに準拠してるとのことで、メソッドも同じものが使えます。ObservableArrayと同じく、追加のメソッドもあります。

Modifiers

↑の例でもしれっとでてきた`asMap`みたいなやつのこと。
Observableな値を作るときに、その振る舞いを変えられる機能です。

他にもいくつかあるのでそれらを。

asFlat

自分はObservable、配下はObservableにしない場合に。

class Store {
  constructor() {
    extendObservable(this, {
      arr1: [],
      arr2: asFlat([]),
      obj1: {},
      obj2: asFlat({}),
    });

    this.arr1.push(1);
    this.arr2.push(1);

    this.arr1.push({ a: 1 });
    this.arr2.push({ a: 1 });

    this.obj1 = { a: 1, b: { c: 1 } };
    this.obj2 = { a: 1, b: { c: 1 } };
  }
}

const store = new Store();

// true false true
console.log(isObservable(store.arr1), isObservable(store.arr1[0]), isObservable(store.arr1[1]));
// true false false
console.log(isObservable(store.arr2), isObservable(store.arr2[0]), isObservable(store.arr2[1]));

// true false true
console.log(isObservable(store.obj1), isObservable(store.obj1.a), isObservable(store.obj1.b));
// true false false
console.log(isObservable(store.obj2), isObservable(store.obj2.a), isObservable(store.obj2.b));

というわけで、`asFlat()`で作ったオブジェクトの「配下のオブジェクト」が、Observableじゃなくなる。
サーバーからjsonデータをfetchしてぶっこんで表示用に使う、みたいなケースではコレを付けることが多いのではないかなーと。

個人的には一番よく使うModifierです。

asReference

そのまんまですが、Observableにしない場合。

class Store {
  constructor() {
    extendObservable(this, {
      data1: {},
      data2: asReference({}),
      data3: asFlat({})
    });
  }
}

const s = new Store();

// Observableなのは
s.data1 = { a: 1, b: { x:2 } }; // `data1`とその配下すべて
s.data2 = { a: 1, b: { x:2 } }; // なし
s.data3 = { a: 1, b: { x:2 } }; // `data3`だけ

ドキュメントには最も一般的とか書いてあったけど、まあならそもそもObservableにしなくて良いやんって感じではある。

asStructure

その名の通り、構造的にデータが変化したときだけ反応するようになる。

class Store {
  constructor() {
    extendObservable(this, {
      screenSize: {
        width: 0,
        height: 0
      },
      viewPortSize: asStructure(() => {
        return {
          width:  Math.max(this.screenSize.width,  1000),
          height: Math.max(this.screenSize.height, 800)
        };
      }),
    });
  }
}

const s = new Store();

window.onresize = () => {
  transaction(() => {
    s.screenSize.width  = window.innerWidth;
    s.screenSize.height = window.innerHeight;
  });
};

普通にやってると、画面の`width`が1000↓、`height`が800↓のときでも`onresize()`が動いて、いっつも`{ width: 1000, height: 800 }`が返ってきちゃう。
でもそんな時は反応して欲しくない!ときに使うのがこのModifier。

もひとつ例を。

class Store {
  constructor() {
    extendObservable(this, {
      x: 0,
      y: 0,
      // さっきと違う座標の時だけ反応
      ponit: asStructure(() => {
        return [this.x, this.y];
      }),
    });
  }
}

const s = new Store();

window.onclick = (ev) => {
  transaction(() => {
    s.x = ev.x;
    s.y = ev.y;
  });
};

`transaction()`はまとめてアップデートするための機能で、また次回以降に紹介します。

というわけで、

入れ子のプロパティーを作ろうとするとasStructureという専用の構築子を要求されたりしだして、だるい感じ。

MobXでReactのステート管理をする - Qiitaより

これは何かの勘違いですね・・。


ちなみに、`computed`とか`action`もModifierの仲間らしいけど、これもまぁまた次回以降に。