next.js + mobxに入門してみた

[Nextremer Advent Calendar 2017](https://qiita.com/advent-calendar/2017/nextremer)の8日目。

最近next.jsが流行ってるぽいので、同じく流行ってるmobxとともに入門してみた。

今回作ったものは[こちら](https://github.com/k-tada/nextjs-mobx-example)

# next.js
https://github.com/zeit/next.js/

[now](https://zeit.co/now)を提供しているZeitが提供するReactアプリケーションを簡単に作成するためのフレームワーク

# 何がいいの？
### 1. 環境構築が超簡単
一番大きいメリットとして、環境構築の容易さが挙げられる

通常、Reactプロジェクトを構築する場合、webpackやbabelなどのライブラリのインストール、設定を行う必要がある。これが毎回だと何気に結構（かなり）面倒くさい

next.jsでは、これらの構築を一切行う必要が無い
必要なことは `npm install -S next react react-dom`を実行することだけ

これだけでReactの実装をはじめられる
更にホットリロードの機能まで備わってる
言うことなしに便利

### 2. ルーティング設定が不要
Reactにはルーティングの機能が備わっていないため、自分で`react-router`などのライブラリを導入して設定する必要がある
しかもコンポーネントでルーティングを設定するため、ルートコンポーネントが超煩雑になる

next.jsではこれらの設定は不要で、`pages`ディレクトリを作ってその下にReactコンポーネントを置くだけ
`pages/index.js`ファイルを置けば`/`にマッピングされ、
`pages/about.js`ファイルを置けば`/about`にマッピングされる

もちろん`components`ディレクトリを容易して`pages`配下のコンポーネントから共通部品を読み込むことも出来る

## 使ってみる
### Hello world
適当なディレクトリで、以下を実行

```bash
yarn init -y
yarn add next@beta #beta版を利用
yarn add react react-dom
mkdir pages
```

pagesディレクトリにindex.jsを作成

``` javascript:pages/index.js
export default () => <div>Hello Next.js</div>
```

NPM ScriptにStart用コマンドを追加

```javascript:package.json
  "scripts": {
    "start": "`npm bin`/next"
  },
```

next.js実行

```bash
yarn start
```

これで`http://localhost:3000`にアクセスすればHello Next.jsと表示されるはず。

# mobx
[mobx](https://github.com/mobxjs/mobx)はシンプルなステートマネージャで、Reactのステート管理に良いのではないかと巷で話題らしい

Reactと併せて利用する場合は`mobx-react`にてmobxとreactを紐付ける（react-reduxのようなもの）

> ※ 事前に。以下の例ではBabelのDecoratorを使ってます

## 概要
管理したいデータをmobxの`observable`として定義し、データを利用する側をmobx-reactの`observer`として定義する

```javascript
class Counter {
  @observable count = 0;
}

@observer
class App extends React.Component {
  render() {
    return <div>count: {this.props.counter.count}</div>
  }
}

render(<App counter={new Counter()} />, document.getElementById('main'));
```
これで`Counter.count`が変更されるとReact側に変更が反映される

`count`を変更するためのメソッドを追加してみる

他の値を利用する場合は`computed`
値を変更したい場合は`action`を使う（`action.bound`を使うことで`this`を束縛できる）

```javascript
import { observable, action, computed } from 'mobx';

export default class Counter {
  @observable count = 0;

  @computed get twice() {
    return this.count * 2;
  }

  @action.bound
  inc() {
    this.count = this.count + 1;
  }

  @action.bound
  dec() {
    this.count = this.count - 1;
  }

  @action.bound
  async asyncInc() {
    await new Promise(resolve => setTimeout(resolve, 1000));
    this.count = this.count + 1;
  }

  @action.bound
  async asyncDec() {
    await new Promise(resolve => setTimeout(resolve, 1000));
    this.count = this.count - 1;
  }
}
```

これらをReactから使ってみる。

```javascript
@observer
class App extends React.Component {
  render() {
    const { counter } = this.props;
    return (
      <div>
        <div>count: {counter.count}</div>
        <div>count*2: {counter.twice}</div>
        <button onClick={counter.inc}>+1</button>
        <button onClick={counter.dec}>-1</button>
        <button onClick={counter.asyncInc}>async +1</button>
        <button onClick={counter.asyncDec}>async -1</button>
      </div>
    );
  }
}
```

# next.js + mobx
next.jsからmobxを使ってみる

## インストール
```bash
mkdir nextjs_mobx
cd nextjs_mobx
yarn init -y
yarn add next@beta react react-dom mobx mobx-react
yarn add -D babel-plugin-transform-decorators-legacy
```

## Babel環境準備
普段はnext.js使うときは不要だが、今回はDecoratorを使いたいので`.babelrc`を用意する

```json:.babelrc
{
  "presets": ["next/babel"],
  "plugins": ["transform-decorators-legacy"]
}
```

## Mobx Store作成
Counterクラスを`stores/counter.js`に↑と同じ内容で作成する
あとはStore取りまとめ用のファイルを作成

```javascript:stores/index.js
import Counter from './counter';
export default {
  counter: new Counter(),
};
```

## next.jsからMobx Storeを読み込む

```javascript:pages/index.js
import React, { Component } from 'react';
import { Provider } from 'mobx-react';
import store from '../stores';
import Main from '../components/Main';

export default class App extends Component {
  render() {
    return (
      <Provider {...store}>
        <Main />
      </Provider>
    );
  }
}
```

### Mobx Storeの値を利用するコンポーネントを作る
> ※ `pages/index.js`でまとめて書ければよかったんだけど何故かできなかったのでコンポーネントを分けてます

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

@inject('counter')
@observer
export default class Main extends Component {
  render() {
    const { counter } = this.props;
    return (
      <div>
        <div>count: {counter.count}</div>
        <div>count*2: {counter.twice}</div>
        <button onClick={counter.inc}>+1</button>
        <button onClick={counter.dec}>-1</button>
        <button onClick={counter.asyncInc}>async +1</button>
        <button onClick={counter.asyncDec}>async -1</button>
      </div>
    );
  }
}
```

これで完了(・∀・)
出来上がったものはこんな感じ
![count.gif](https://qiita-image-store.s3.amazonaws.com/0/16629/42ed987d-3b49-abd5-31f3-a28e49a79648.gif)

# テスト
jestを使ってテストを書いてみる

## インストール

```bash
yarn add -D jest
```

## 準備
next.jsと併用する場合は`.babelrc`を少々変更する必要がある。
jestはデフォルトでbabelのenvをtestにして実行するため、以下のように`.babelrc`でenv:testの設定を作成する。

```json:.babelrc
{
  "env": {
    "development": {
      "presets": ["next/babel"],
      "plugins": ["transform-decorators-legacy"]
    },
    "production": {
      "presets": ["next/babel"],
      "plugins": ["transform-decorators-legacy"]
    },
    "test": {
      "presets": [["env", {"modules": "commonjs"}], "next/babel"],
      "plugins": ["transform-decorators-legacy"]
    },
  },
}
```

## Viewのテスト
Enzymeとかで書く感じかな？今回は省略。
書くなら[この辺](https://github.com/zeit/next.js/tree/canary/examples/with-jest)を参考に。

## MobXのテスト
こんな感じ？

```javascript:__tests__/stores/counter.js
import CounterStore from '../../stores/counter';

describe('CounterStore',() => {
  let store;
  beforeEach(() => {
    store = new CounterStore();
  })
  describe('#constructor', () => {
    it('count expect to be 0', () => {
      expect(store.count).toBe(0);
    })

    it('twice count expect to be 0', () => {
      expect(store.twice).toBe(0);
    })
  });
  describe('#inc', () => {
    it('count expect to be increment', () => {
      store.inc();
      expect(store.count).toBe(1);
    })
  });
  describe('#dec', () => {
    it('count expect to be decrement', () => {
      store.inc();
      expect(store.count).toBe(1);
      store.dec();
      expect(store.count).toBe(0);
    })
  });
  describe('#asyncInc', () => {
    it('count expect to be increment', async () => {
      await store.asyncInc();
      expect(store.count).toBe(1);
    })
  });
  describe('#asyncDec', () => {
    it('count expect to be decrement', async () => {
      store.inc();
      expect(store.count).toBe(1);
      await store.asyncDec();
      expect(store.count).toBe(0);
    })
  });
});
```

# 感想
兎にも角にもnext.jsが超楽
mobxも単純なアプリを作る分には十分使える代物だと思う（複雑なパターンでも使えるかは要検証
Reduxを使う場合に比べて記述量、要する時間ともに圧倒的に少ないのがいい
今後サンプルを作る場合とかは全部これでいいんじゃないかと思ふ