🧊

MobX 3.0.0 の変更点について

MobX 3 released: Unpeeling the onion – Michel Weststrate – Medium

作者の @mweststrate 氏による記事も出てましたねー。

`2.7.0`まで理想的な使い方をしてきたなら、特に大きな変更があるわけではないバージョンアップです。
ただにわかに人気が出つつある気もするので、長いことMobX使ってるマンとして書いておきまーす。

変更点まとめ

mobx/CHANGELOG.md at master · mobxjs/mobx · GitHub

詳しくは↑のログを見てもらうとして、個人的に大きなのは、

  • APIの刷新
    • `observable`な値の作り方が変わった
    • Modifierが消えた
  • Bound actions
  • エラーハンドリングの強化
  • その他

って感じですね。

APIの刷新

MobX的Observableな値を作るためのコードスタイルが少し変わります。

タイプごとの`observable.*`

`2.7.0`までのMobXは、どんな型の値でも共通のAPIでObservable化してました。

// 2.7.0
const arr = observable([]);
const obj = observable({});

これが、

// 3.0.0
const arr = observable.array([]);
const obj = observable.object({});

const any = observable({});

という用に、APIとしても型を明示できるようになりました。
っても中の挙動は変わってないし、`observable()`をそのまま使えば値によって今まで通りに使えます。

さよならModifier

`2.7.0`までのMobXは、渡されたオブジェクトを勝手に「再帰的に」Observableにしてました。
APIからの返り値をただ入れておきたいみたいな場合、巨大なJSON再帰されても・・ってな場合には、Modifierを使ってました。

// 2.7.0
// 再帰
observable(response)

// 1階層だけ
observable(asFlat(response))

ただこのModifier、APIのデザインとして適当・・?っていう話が前からあり。
左辺で定義したいとか、flatってなんやねんとか、そもそもいらねーんじゃねーのとか。

今回のAPI刷新に伴い、ここも変更されました。

// 3.0.0
// 再帰
observable.object(response)

// 1階層だけ
observable.shallowObject(response)

Observableな値を作るには、`observable.*()`なメソッドを使うということで、よりわかりやすくなりました。
この`observable.*`を何層か組合せて実現していくのを、オニオンアーキテクチャというらしい。冒頭の記事のタイトルにもなってる考え方ですね。

// 2.7.0
class Store {
  constructor() {
    extendObservable(this, {
      foo: asFlat([]),
      bar: asMap({}),
    });
  }
}

// 3.0.0
class Store {
  constructor() {
    extendObservable(this, {
      foo: observable.shallow([]),
      bar: observable.map({}),
    });
  }
}

`extendObservable`の場合もこのような感じ。

生き残りModifier

↑のコードを見て、`shallow`にはしたいけど型不定な場合どうしたらいいの?とか、Decorators派なんですけど・・って人もいるので、

  • `observable.deep`
  • `observable.ref`
  • `observable.shallow`

この3つは引き続きModifierとして使えます。

// 2.7.0
class Store {
  constructor() {
    extendObservable(this, {
      todos: asFlat([])
    })
  }
}

class DecoStore {
  @observable todos = asFlat([])
}

// 3.0.0
class Store {
  constructor() {
    extendObservable(this, {
      todos: observable.shallow([])
    })
  }
}

class DecoStore {
  @observable.shallow todos = []
}


ちなみにModifierは、`@observable`、`extendObservable`、`observable.object`でしか使いません。

オブジェクトもクローンされるように

// 3.0.0
const { observable } = mobx;

const a = { x: 1 };
const o = observable(a);

o.x = 2;

console.log(a.x === o.x) // false

`2.7.0`までは、これが`true`でした。

まあそんなに驚きはない・・かと。
ArrayとかMapは今までクローンされてたので、こっちのほうが自然ですね。

Bound actions

個人的に欲しかったやーつ。
公式のログからまるっと持ってきましたが、

class Ticker {
  @observable tick = 0

  @action.bound increment() {
    this.tick++ // 'this' will always be correct
  }
}

const ticker = new Ticker()
setInterval(ticker.increment, 1000)

というわけで、`@action`の明示と`bind(this)`がまとめてできるように!
いやー`action`化するのと`bind`するので`prototype`を2周するのアレだったんですよねー。

ちなみに、`action`化するというのは・・、
`MobX.useStrict(true)`することで、`action`化されたメソッド以外でObservableな値を変更すると警告が出る(そのおかげで関心の分離ができる)ようになります。
そのため、このメソッドは`action`ですと明示することが必要で、それです。

エラーハンドリングの強化

`2.7.0`までのMobXは、なんかエラーや例外があったときに、catchしてwarningをログに出してくれてました。
ただこうしてしまうと、MobXに問題があるのか、ユーザーが書いたコードに問題があってのものなのか、見分けがつきにくかったです。そしてだいたいMobXに問題は無い。

`3.0.0`からのMobXは、なんの警告も出さなくなって、エラーハンドリングは全てユーザーに委ねられるようになります。

その他

他にもBreakingな変更もあるんですけど、そこまで影響ないと思うので割愛。

  • DepricatedなAPIがいろいろ消えたり
  • Flowで型の恩恵が得られるようになったり

もしてます。
Reduxはいらんと思ってるけど生Reactは辛い・・そういうあなたにオススメなMobXの2017年にこうご期待。

というわけで個人的には割と理想の使い方をしてきてたので、Modifierを一括置換するだけで移行完了でした。