React NativeとReduxで野球スコアリング

# 野球スコアリング機能のリリース
今から1年以上も前になるが[自社プロダクト](https://tmhub.jp/)で野球スコアリング機能をリリースした。おかげさまで、リリース以降多くのアマチュア野球チームで利用されるようになり、今ではアプリの目玉機能の1つになっている。私はiOS/Androidアプリ側の開発を主に担当していたので、その実装の大枠を紹介する。

# APIとアプリの責務の分離
アプリとAPIは明確に役割が分かれている。おかげでお互いに自分の責務に集中して開発することができた。

### アプリでやること
- ユーザーが野球のプレイを入力するためのUIを提供する。
- ユーザーが入力したプレイ（打撃、投球、走塁、守備）をAPIへ送信する。
- APIから返却されたスコア（得点、アウトカウント、塁上の走者、成績、etc）をアプリで表示する。

### APIでやること
- アプリから送信されたプレイをもとにスコア（得点、アウトカウント、塁上の走者、成績、etc）を計算する。
- アプリに、スコアの計算結果を返却する。

![Screen Shot 2019-06-30 at 11.05.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49022/f26b88de-2b98-a964-09e7-7cb163e9ce7a.png)

GOで書かれたAPIはRESTで設計されている。レスポンスはJSON形式で返却される。APIの開発は弊社CTOが1人で担当していて、実装がどうなっているか私も知らない。

# React Native
アプリはReact Nativeで実装していて、ES6で書かれたソースコードの95%はiOS/Androidで共用している。当初、野球の混みいったUIをReact Nativeでどこまで表現できるのかは、未知の領域だったが、アニメーション、ドラッグ＆ドロップ、処理速度も問題なく実装できた。React Nativeでのアニメーションやドラッグ＆ドロップの処理については、すでにいろんな記事があるのでここでは説明しない。

# Redux
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49022/6fa1b7b7-cc73-c260-477e-7df9510d1159.png" />

Stateの管理はReduxを採用していて、この野球スコアリング機能は、ただひたすらにReactとReduxのサイクルに準拠した繰り返し作業の結果である。

```
1. Componentはpropsとして渡ってくるstateをみて表示の出し分けをする。
2. Componentから発火するActionを通じreducerでstateに変更を加える。(APIへの通信も含む)
3. 1.に戻る  
```

野球は複雑なルールのため、stateはかなり巨大になってしまっているが、stateの変更は必ずActionを通じて行われるというReduxの制約によって、stateの変化をActionのログから１つ１つ追えることができて安心感があった。

# Component構成
野球のスコアリング機能の特徴は1球ごとの詳細なプレイ(スポーツの世界ではPlay-by-Playと呼ばれる)を記録できること。画面遷移は、ナビゲーションライブラリなどは使わず、ベースコンポーネント上に重なった画面レイヤーをpropsの値をみて出し分けることで実現している。画面レイヤーはReact Componentのことだが、画面上に重なるイメージでそう呼んでいる。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49022/f78c5bcd-8233-6414-2cd8-699a634cd2cd.png">
画面レイヤーの重なりのイメージ。この他にもたくさんのレイヤー、モーダルがベースコンポーネント上にのっかっている。

## ベースコンポーネント
<img width="240" alt="Screen Shot 2019-06-29 at 15.21.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49022/a8f5a510-ce55-dcd6-4928-529aa7fbb05b.png">

プレイを記録する操作を開始する基盤となり、各種モーダル、レイヤーの表示/非表示を制御するコンポーネント。表示/非表示の判断はpropsから渡ってくるstateを参照して判断。ベースは"基本"という意味で、野球の塁のことではない。

```jsx
// ベースコンポーネント
class BaseballPlayByPlayScore extends Component {
  render() {
    const {
      fieldingLayer,
      runnerLayer,
      selectPlayModalVisible,
    } = this.props;

    return (
     {/* 野球グラウンドの背景画像 */}
     <Image source={groundImg}  ... />

     {/* 守備/打者/投手選手の表示 */}
     <Player position={1} ... />
     <Player position={2} ... />

     {/* 投球ボタン　*/}
     <PitchButton ... />

     {/* Undo/Redoボタン */}
     <Undo ... />
     <Redo ... />

     {/* 守備記録レイヤー */}
     {fieldingLayer
       && (
         <Fielding ... />
       )
     }

     {/* 走塁プレイ記録レイヤー */}
     {runnerLayer
       && (
         <Runner ... />
       )
     }

     {/* プレイ選択モーダル */}
     <SelectPlayModal isOpen={selectPlayModalVisible} ... />
    );
  }
}
```

## 走塁記録レイヤー
<img width="250" alt="Screen Shot 2019-06-29 at 16.26.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49022/da492e58-322d-d594-f11f-445c598a0eda.png">
走者をドラッグし、ベース付近にドロップするとセーフかアウトかを選択できるUIを持ったレイヤー。

### コンポーネント分割するとドラッグが速くなった
当初、走塁記録レイヤーはコンポーネントとしては独立しておらず、ベースコンポーネントの中に直接書いていたのだが、リファクタリングのため、コンポーネントとして分割すると、ドラッグの操作が体感的にもわかるぐらい速くなった。

走者をドラッグするたびに、走者の座標(x,y)を取得し `this.setState()` を実行して、座標データを `this.state` に保持していたので、その都度 `render` が走り、大きなベースコンポーネントの中でDOMの差分更新が行われて処理が重くなっていたのだと思われる。コンポーネントを分割して、小さくすることでドラッグが速くなった。これを契機に、コンポーネントとして分割できるものはどんどんしていった。

```jsx
// ベースコンポーネント
class BaseballPlayByPlayScore extends Component {
  componentWillMount() {
    // ドラッグの処理をゴニョゴニョ
  }
  render() {
     {/* 走塁プレイ記録 */}
     {/* 最初は直接走者アイコン直接ベースコンポーネントに記述 */}
     {runnerLayer
       && (
         <View>
           <RunnerIcon ... />
           <RunnerIcon ... />
           <RunnerIcon ... />
         </View>
       )
     }
  }
}
```

**↓コンポーネントに分割するとドラッグが爆速！**

```jsx
// ベースコンポーネント
class BaseballPlayByPlayScore extends Component {
  render() {
     {/* 走塁プレイ記録レイヤー */}
     {/* コンポーネントに分割。ドラッグ処理もコンポーネントの中に内包 */}
     {runnerLayer
       && (
         <Runner ... />
       )
     }
  }
}
```

# 巨大なStateとReducer
野球のプレイパターンを全て網羅しているため、Reducerでのプレイごとの処理分岐の数がかなり多い。
さらに野球のスコアを記録するUIを構築する上で状態遷移が多岐にわたるため、Stateが持つプロパティも多い。
ここらへんは、もう少し見通しの良いコードに改善できる余地はあるかもしれない。
とはいえ、前述した通りReduxのデータフローにのっとっているので、根幹となる構造は非常にシンプルなものになっていると思う。