MobXの observable と action について

[先日の投稿](http://qiita.com/Takepepe/items/59d1396c25c8a699c41c)に続き第３弾です。前回はMobXAPIの話を差し置いて、いきなりReactMobXの設計論になってしまいました。本稿では個人的にMobXのちょっと分かり辛い・気をつけないといけないなと感じた、基本の部分を綴っています。ゆるい感じでピックアップしながら、気ままに記事を書いていこうと思います。

##### 【DevTools】

もうMobXのコードを書いてみた方で、DevToolsを導入していない方は是非いれてみてください。[こちら](https://mobx.js.org/best/devtools.html)のページで`<DevTools / >`をマウントして導入する方法が紹介されていますが、[ChromeExtension](https://chrome.google.com/webstore/detail/mobx-developer-tools/pfgnfdagidkfgccljigdamigbcnndkod?hl=ja)をいれておけば、マウント無しで同じ機能が利用出来る様になります。本稿の記事を読み解くにあたり、役にたつはずです。



# observable型について

observable値には、JSプリミティブ、参照、プレーンオブジェクト、クラスインスタンス、配列、マップなどがあります。`@observable`は、常に最適な observable型を作成しようとします。なので、何となしで `@observable`な値を作ってしまうと危険です。

 observable型は、階層をどこまで辿って追跡可能な値とするのか、modifiers で指定することが出来ます。modifiersには「ref」「shallow」「deep」があり、何も指定しなければ、常に`.deep`となります。

```store.js
class Store {
  @observable.ref      collectionRef = []
  @observable.shallow  collectionShallow = []
  @observable/*.deep*/ collectionDeep = []
}
```

`.ref`は最も浅い追跡をします。下記の様な挙動です。Objectの参照が変わることをフックに、observerに伝達させます。

```store.js
class Store {
  @observable.ref collection = []
  @action editCollection = () => {
    // 参照が変わるので、伝達する（新しい配列を参照）
    this.collection    = [{ name: 'myName' }]
    // 参照はそのままなので、伝達しない
    this.collection[0] = { name: 'myName' }
  }
}
```

`.shallow`は一階層目まで追跡、変更が伝達されます。参照の変更においても伝達されます。

```store.js
class Store {
  @observable.shallow user = {
    name: 'myName',
    friends: [{ name: 'firendName' }]
  }
  @action editUser = () => {
    // 1階層目なので、伝達する
    this.user.name = 'newName'
    // 2階層目より深いので、伝達しない
    this.user.friends[0].name = 'newFriendName'
    // 参照が変わっているので、伝達する
    this.user = { name: 'myName', friends: [{ name: 'newFriendName' }]}
  }
}
```

`.deep`はどこまで深くなっても追跡、変更が伝達されます。デフォルトの挙動がこれです。気をつけなければいけないのが、サンプルコードの下部、新しいObjectを参照しているところです。

```store.js
class Store {
  @observable user = {
    name: 'myName',
    friends: [{ name: 'firendName' }]
  }
  @action editUser = () => {
    // 伝達する（1度だけ）
    this.user.friends[0].name = 'newFriendName'
    // 伝達する（3度も）
    this.user = { name: 'myName', friends: [{ name: 'newFriendName' }]}
  }
}
```

deepで追跡しているため、以下の3点が observable値とみなされ、
**observerへの伝達が3度も走ることになります。これは`this.user`を参照している Reactコンポーネントで、3度リレンダリングされることを意味します。**

- this.user.name
- this.user.friends
- this.user.friends[0]

この点をきちんと把握しておかないと、知らず知らずのうちにパフォーマンスが劣化してしまいます。APIから巨大なjson配列を取得して `@observable = []` などに代入すると、とんでもなく遅くなります。こういったものには `@observable.ref = []` の様に、要件に応じた modifier を付与しましょう。

また上記は、伝達するかしないかの話であって、内容は変化しています。別コンポーネントの変化によって再レンダリングされた時に変化するので、その挙動に少し戸惑うかもしれません。

# actionの存在意義
カッチリ書きたい人向け、と考えて良さそうです。
とりあえず `useStrict(true)` を宣言してみましょう。

```main.js
import { useStrict } from 'mobx'
useStrict(true)
```

`@action` でデコレートされていない関数以外から observable値を変更しようとすると、いろいろ怒られます。`useStrict(true)` 宣言も `@action` デコレータの使用も、optional とのことですが、堅牢に作りたい場合やログを追える様になるので、個人的にはactionも、strictモードも使う派です。

```
Error: [mobx] Invariant failed: Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. 
```

-  action を付与することで、stateを変更する関数であることを明示的に宣言する。
- observable値を変更したり副作用を伴う関数は action 使用が推奨される。
- DevToolsでログ出力される様になる。
- strictモードを有効にしている場合、action を経由しないobservable値の変更は怒られる。
- コールバックなどの副作用から、observable値を変更しようとする場合、`runInAction` 関数でラップしなければ怒られる。
- `runInAction` 関数は第一引数にDevToolsのログを設定できる。

ちなみに、`@action`デコレータも下記の様にログ用の文字列を指定することが出来ます。日本語もござれ。

```someStore.js
@action('値を1追加') increment = () => this.count++
```
![image](https://qiita-image-store.s3.amazonaws.com/0/59071/f5cdf783-1195-4dbe-a4a8-a6b06c90526b.png)