SPAの知識ゼロの状態から、React＋Reduxの開発環境構築までにやったこと 

Webアプリケーションを開発する際に、「今ならSPA（Single Page Application）っしょ」となり、フレームワークの選定からやらせてもらう機会があったので、そのときにやったことを共有します。

## 前提
本記事作成時の筆者のスキル

* Webアプリケーションは作ったことがあるが、言語はJava
* SPA is 何？
* Javascriptはかじった程度の知識

## フレームワークの選定
Googleトレンドや、実績からSPAのフレームワークを以下に絞りました。

* React
* AngularJS
* Vue.js

その時人気だったReactにしておけばいいだろと、Reactにしました。
※近年、Vue.jsが勢力を伸ばしてきているらしいです。

## React + α
Reactは様々なライブラリと組み合わせるのが普通のようです。
npm installでどんどんライブラリを追加していくことになりますが、ここでは開発手法に大きく関わるライブラリだけ。

* TypeScript：Javascriptに静的型付けをしてくれる、AltJS。正直これ初心者には辛い。
* Redux：stateの管理方法に大きく関わるライブラリ  
  参考記事：https://qiita.com/mpyw/items/a816c6380219b1d5a3bf
* @reduxjs/toolkit：ReduxのActinとReducerをまとめて記述できるツール。Reduxチーム公式。  
  参考記事：https://qiita.com/Ouvill/items/a76e9cbce569d01f2931
* redux-saga：タスクをうまいこと処理してくれる"ミドルウェア"という概念。APIリクエストなどの非同期処理で使用する。  
  参考記事：https://qiita.com/kuy/items/716affc808ebb3e1e8ac  
  ※while(true)よりも、takeLatestをよく使いました。
* Material-UI：見た目をいい感じにしてくれるライブラリ。これは無くてもよかったかも。
* react-intl：メッセージ管理や多言語対応をできるようにするライブラリ  
  公式ドキュメント：https://github.com/formatjs/react-intl/blob/master/docs/README.md

執筆時点での最新バージョンはそれぞれ以下の通りです。あまり古くなっているようなら、参考にならないかもしれません。

| プロダクト | version |
| ---- | ---- |
|React|16.12.0|
|Redux|4.0.5|
|TypeScript|3.7.5|
|redux-saga|1.1.3|
|Material-UI|4.9.2|
|react-intl|3.12.0|

### エディター
これも流行りなのでVSCodeを使用しました。
結果的に、nodeやgitのコマンドもターミナルのペインで実行できるので、かなり使いやすかったです。

#### 設定と拡張機能をチーム内で共有する
エディタの設定は、プロジェクト直下の.vscode/settings.jsonというファイルで共有できます。
拡張機能は、.vscode/extensions.jsonというファイルを置くことで、VSCode起動時にポップアップが出るようになります。
フォーマッター、保存時の自動フォーマットなどの設定は共有しておくと便利です。

## プロジェクトの作成
create-react-appというコマンドを使います。これを使うと起動スクリプトやwebpackのコンフィグなどが隠された状態で、カスタマイズできないので、すぐにejectというコマンドで素のnodeで扱えるようにします。

```bash
npx create-react-app my-app
cd my-app
npm run eject
```

## プロジェクトの設定

### ディレクトリ構成を考える

こんな感じになりしました。「これが正解！」というパターンは無いようなので、あくまで一例として。
<details><summary>ディレクトリ構成（クリックで開く）</summary><div>

```
<root>          
├─public          画像ファイルなど。srcから見たとき、"/"がpublic直下
├─build           ビルドされたファイルが作成される先
├─config          create-react-appで勝手の作成される
├─scripts         create-react-appで勝手の作成される＋自作のnpmスクリプトを作る場合はここ
├─node_modules          
└─src             src以外のファイルはほとんど触らない    
  ├─i18n          メッセージのyamlと、それを管理するJS
  ├─modules       actionとreducerをまとめたもの
  ├─containers    1画面1コンテナという考え方で、コンテナコンポーネントを作成する。
  ├─comopnents    コンポーネント。画面ごとにサブディレクトリを切る      
  ├─sagas         非同期処理を扱う処理 
  ├─scss          .scssファイル
  └─utils         共通で使う処理。Validatorなど
```

</div></details>

### importを絶対パス指定でできるようにする

これデフォルトじゃないんかい。。。と思いながら設定しました。
方法はいくつかあるようですが、TypeScriptを使っているのでtsconfigで設定できる方法を採用しました。
これをやらないと、"../../../../"地獄になります。

```bash:いつもの
npm i --save tsconfig-paths
```
```json:tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src"
     ~~~後略~~~
}
```

### メッセージ管理方法を考える

#### yamlでメッセージ管理できるようにする

yaml-flat-loaderというライブラリを使用して、yamlをjsonとして読み込めるようにします。

```bash:いつもの
npm i --save-dev yaml-flat-loader
```
```js:webpackconfig.jsのrulesに追記
{
  test: /\.yml$/,
  use: [{ loader: 'json-loader' }, { loader: 'yaml-flat-loader' }]
},
```

#### メッセージのキーを補完できるようにする
yamlでメッセージ管理する弊害として、コピペがし難いこともあり、補完できるように一手間加えました。
メッセージのキーをオブジェクトとして参照できるようにすることで、メッセージキーの変更やキーの指定ミスがコンパイルエラーになるというメリットもあります。
react-intlの正しい使い方をしているなら、[こちらの記事](https://medium.com/@akameco/%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E6%99%82%E4%BB%A3%E3%81%AEi18n-ef8d5536c6e7)が参考になりますが、JSでデフォルトメッセージを定義して、各言語はyamlというのは面倒です。
メッセージはyamlファイルだけで完結したいので、yamlファイルのキーをjsonにします。

スクリプトを作成し、messageKeysというオブジェクトが各ファイルから参照できるようにします。
ja.ymlに存在するキーをJSONにしています。

<details><summary>スクリプト詳細（クリックで開く）</summary><div>

```bash:まずは必要ライブラリのインストール
npm install --save-dev js-yaml
```

```javascript:scripts/messageGen.js
'use strict';

var yaml = require('js-yaml');
var fs = require('fs');
 
// Get document, or throw exception on error
try {
  let doc = yaml.safeLoad(fs.readFileSync('src/i18n/ja.yml', 'utf8'));
  let str = "export const messageKeys =";
  str += JSON.stringify(keyToValue("", doc));
  str += ';\n';
  str += "export default messageKeys;"
  fs.writeFile("src/i18n/messageKeys.tsx", str, (err) => {
    if (err) {
      throw err;
    }
  });
} catch (e) {
  console.log(e);
}

function keyToValue(parentKey, param) {
  let ret = param;
  for (let [key, value] of Object.entries(param)) {
    if (typeof value === 'object') {
        keyToValue(parentKey + key.toString() + ".", value);
    } else if (typeof value === 'string') {
      ret[key] = parentKey + key;
    }
  }
  return ret;
}
```

```diff:package.json（抜粋）
"scripts": {
-  "start": "node scripts/start.js",
+  "start": "node scripts/messageGen.js && node scripts/start.js",
+  "message": "node scripts/messageGen.js"
},

```

```typescript:使用例
import messageKeys from "src/i18n/messageKeys";
~~~中略（コンポーネント内）~~~
const intl = useIntl();
intl.formattedMassage({ id: messageKeys.some.message.id });
```

</div></details>

"npm run message"でsrc/i18n/ja.ymlに存在するキーをjsonにしてくれます。
※エディタ上で補完できるようにするには、messageKeys.jsを一度開く必要がありました。


### スタイリング方法を考える

[こちらの記事](https://qiita.com/lightnet328/items/218eb1c4a347302cc340)を参考に、"1. クラス名によるスタイリング"と"3. CSS Modules"で悩みましたが、CSS（SCSS）によるスタイリングを採用しました。
理由は、

* 業務ロジックやイベント処理を記述しているコンポーネントファイルに、見た目のことまで書きたくない
* 普通のSCSSの方が書ける人が多い
* ぶっちゃけCSS Moduleよくわからなかった

あたりです。
ただし、これには以下のデメリットがあります。

* 普通にscssをimportすると、Reactはstyleタグを生成するので影響範囲は全体ということになる
* 上記動作により、複数回importされると無駄なstyleタグが増えることになる

よって、コンポーネントのrootとなる要素にclassを設定し、その下にネストしたSCSSを書くようにしました。
（結局は開発者の運用努力なので、改善すべき部分ではあります。）


### 結び
まだまだ色々なことをやった（ような気がします）が、以上が開発開始時の"進め方"を決める段階で時間をかけて考えた部分になります。
これからReactの開発を始める際の参考になれば幸いです。