MobXを学ぶ(初級)

# 前置き
大層なタイトルがついていますが、単純に筆者の学習録です。

公式を読む前の前座程度に考えてください。

# MobXとは
[Introduction](https://mobx.js.org/index.html)を読んでも、イメージができなかったので、
下記の記事がとてもわかりやすかったです。

参考：
[これからMobXをはじめる人へ](https://lealog.hateblo.jp/entry/2016/12/15/113825)

>ReactiveなObservableな値を用意して、それを変更したら関連するものも全部更新されて・・と、そういういわゆるState管理の層だけをやるライブラリです。

>`mobx-react`というバインディングがあって、Observableな値を更新すると勝手にコンポーネントの`render()`が走るみたいにできます。

## 読み方がわからない
読み方は・・これなんでしょうね・・。
Mob X。
モブクス、マブクス。

FluxやReduxが、〜ックスだから、
日本人だとモブックスなのかなぁ。

# React + MobX
## 「とりあえず使う」ために抑えること
|覚えるもの|説明|備考|
|:--|:--|:--|
|mobx.observable|State管理で監視したい値につけるもの||
|mobx.computed|observableな値に依存する値|Excelのセル参照をイメージするといい|
|mobx.action|observableな値を変更する関数にはactionをデコレート|トランザクション等のメリットがある|
|mobx-react.observer|Reactコンポーネントをリアクティブなコンポーネントに変換する|mobx.observableな値を使うために必要、くらいのレベルで良さそう|

参考：
[これからMobXをはじめる人へ](https://lealog.hateblo.jp/entry/2016/12/15/113825)
[MobXでReactのステート管理をする](https://qiita.com/mizchi/items/2b6de4e5a853f7a578a6)
[MobXの observable と action について](https://qiita.com/Takepepe/items/cf22f1a17df30a76f170)

## 何かを作るために意識しておくこと
**[1]用意するものは`Store`と`コンポーネント`。**
**[2]Storeには、表示に必要なものを全てぶち込む**
例えば、

- 表示したい値
- 値を変更するための関数

**[3]コンポーネントにStoreをぶち込む。**

## とりあえずやってみる
### Storeを用意する
- 数値
- 2倍にした数値
- 数値の入力

をStoreとして定義します。

```js:src/stores/sample.js
import {observable, computed, action} from 'mobx';

export default class SampleStore {
    @observable num = 0;

    @computed get double() {
        return this.num * 2;
    }

    @action changeNum = (newNum) => this.num = Number(newNum);
}
```

### Storeを使うコンポーネントを用意する
作成したStateをpropsで渡される想定です。

デコレーションでリアクティブコンポーネントに変換している以外は、
ただのReactの記述です。

```jsx:src/components/sample.js
import React, {Component} from 'react';
import {observer} from 'mobx-react';

@observer
class SampleComponent extends Component {
    render() {
        return (
            <div>
                <input type="number"
                    value={this.props.store.num}
                    onChange={(e) => this.props.store.changeNum(e.target.value)}
                />
                <div>{this.props.store.num}</div>
                <div>{this.props.store.double}</div>
            </div>
        );
    };
}

export default SampleComponent;
```

### 用意したコンポーネントを使ってみよう
```jsx:src/app.js
import React, { Component } from 'react';
import SampleStore from './stores/sample';
import SampleComponent from './components/sample';

const store = new SampleStore();

class App extends Component {
    render() {
        return (
            <div>
                <SampleComponent store={store} />
            </div>
        );
    }
}

export default App;
```

参考：
[The gist of MobX](https://mobx.js.org/intro/overview.html)

# Storeをオシャレに取り出す
- [次のReact状態管理はMobXにする理由](tps://qiita.com/Takepepe/items/8172333655442314eb55)
- [0からはじめる MobX Part.2](https://lealog.hateblo.jp/entry/2016/09/18/125202)

上記の記事を拝見していたら、**inject**なる言葉が。

どうやら[mobx-reactに用意されているもの](https://github.com/mobxjs/mobx-react#user-content-provider-and-inject)っぽい。

そうか・・mobxの公式を網羅できれば極まるかと思ったが、そんなわけないか。

>Provider is a component that can pass stores (or other stuff) using React's context mechanism to child components. This is useful if you have things that you don't want to pass through multiple layers of components explicitly.

>inject can be used to pick up those stores. It is a higher order component that takes a list of strings and makes those stores available to the wrapped component.

[ReactのContext](https://reactjs.org/docs/context.html)の仕組みを使っているらしい。
※知らない人(主に私)は、先にこっちを読んだ方がイメージがつかみやすいかも。

Providerは、子コンポーネントにStoreを渡す仕組みで、
コンポーネントが階層構造になっているとき、Storeを意識させたくないときに便利。

injectは、使いたいコンポーネントでStoreを取得するのに使う。

って感じ・・？

ちょっとやってみましょう。

## Providerとinjectを使ってみる
「とりあえずやってみる」で作成したコンポーネントを無駄にもう１階層深くしてみましょう。

### ProviderにSampleStoreをセット
子コンポーネントがthis.props.sampleStoreを使えるように、
Providerを用意します。

```jsx:src/app.js
import React, { Component } from 'react';
import {Provider} from 'mobx-react';
import SampleStore from './stores/sample';
import MiddleComponent from './components/middle';

const store = new SampleStore();

class App extends Component {
    render() {
        return (
            <div>
                <Provider
                    sampleStore={store}
                >
                    <MiddleComponent />
                </Provider>
            </div>
        );
    }
}

export default App;
```

### Storeを用いないレイヤーを用意する
俺Store使わんしwww
というコンポーネントを用意。

Storeがまるっきり出てこないのでとても綺麗。

```jsx:src/components/middle.js
import React from 'react';
import SampleComponent from './sample';

export default () => <SampleComponent />;
```

### Storeを用いるレイヤーを用意
ここでinjectする。

this.props.sampleStoreが使えるはずなので、
Storeから値をとったり、更新したりする。

```jsx:src/components/sample.js
import React, {Component} from 'react';
import {inject, observer} from 'mobx-react';

@inject('sampleStore')
@observer
class SampleComponent extends Component {
    render() {
        return (
            <div>
                <input type="number"
                    value={this.props.sampleStore.num}
                    onChange={(e) => this.props.sampleStore.changeNum(e.target.value)}
                />
                <div>{this.props.sampleStore.num}</div>
                <div>{this.props.sampleStore.double}</div>
            </div>
        );
    };
}

export default SampleComponent;
```

# 付録
## 学習環境
Node.js(express) + React + mobx
で想定。

参考：
[How to (not) use decorators](https://mobx.js.org/best/decorators.html)

### 準備
```bash:express
mkdir sample
express --view=pug sample
cd sample
npm install
```

```bash:必要なものを入れる
npm install -D \
@babel/cli \
@babel/core \
@babel/plugin-transform-runtime \
@babel/preset-env \
@babel/preset-react \
@babel/preset-stage-2 \
@babel/runtime \
webpack \
webpack-cli \
babel-loader@8.0.0-beta.0

npm install react react-dom mobx mobx-react --save
```

```json:package.jsonのscriptsに以下を足しておく
  "scripts": {
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch",
    "start": "node ./bin/www"
  },
```

```js:webpack.config.babel.js
const path = require('path');

exports.default = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'public'),
        filename: 'main.js'
    },
    devtool: 'inline-source-map',
    module: {
        rules: [
            {
                test: /\.(js)$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            ['@babel/preset-env', { 'modules': false }],
                            '@babel/preset-react',
                            ['@babel/preset-stage-2', { 'loose': true, 'decoratorsLegacy': true }]
                        ]
                    }
                }
            }
        ]
    }
};
```

### Reactの表示まで
```js:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app.js';

ReactDOM.render(<App />, document.getElementById('root'));
```

```js:src/app.js
import React, { Component } from 'react';

class App extends Component {
    render() {
        return (
            <div>
                AppComponent
            </div>
        );
    }
}

export default App;
```

```lang:index.pug
extends layout

block content
  div#root
  script(src='/main.js')
```

```lang:表示
npm run dev
npm start
# localhost:3000にアクセス
```