Reactで開発するときのプロジェクトセットアップ手順

今更ながらReactに入門しました。

ということで、Reactでアプリケーションを開発していくときに、何をどうセットアップしていくかをまとめてみたいと思います。

## 宣伝
本題に入る前に少し宣伝させてください。

今回、Reactでタスクのトラッキングアプリを作ってみました。
もしよかったら、一度さわってみてもらえると嬉しいです。(すみません、PCのChromeでみてください)
https://tasktrack.web.app/tasks

こちらに紹介記事を書いているので、もしよかったらこちらも読んでみてください。
https://note.com/digitalshuufei/n/nf7e9d9644d13

## この記事でやること

- create-react-appでプロジェクト作成
- eslintの設定
- formatter(Prettier)の設定
- eslintとPrettierの共存
- CSS in JSツール(Emotion)の導入
- Reduxの導入

実際のコードはこちらにあります。
https://github.com/shuufei/react-project-setup

## create-react-appでプロジェクト作成

公式の手順を参考に作成していきます。
https://github.com/facebook/create-react-app#creating-an-app

Typescriptで開発したいので、 `--typescript` オプションをつけます。

```bash
> npx create-react-app test-app --typescript
```

プロジェクトの作成が終わったら、一度動かして確認してみます。

```bash
> cd test-app
> npm run start
```

localhost:3000でアクセスして下記のよう画面になっていれば問題ないかと思います。
![localhost_3000_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85232/375c73d4-8bc8-7877-c435-f150a42d7e3c.png)


問題なく起動できることが確認できたら、このタイミングでpackageを整理しておくと良いかもしれません。
create-react-appでプロジェクトを作ると、開発時にしか使わないpackageもdependenciesに入っているので、devDependenciesに移動してインストールしなおします。

修正後のpackage.jsonは下記です。

```diff:package.json
   "dependencies": {
-    "@testing-library/jest-dom": "^4.2.4",
-    "@testing-library/react": "^9.5.0",
-    "@testing-library/user-event": "^7.2.1",
-    "@types/jest": "^24.9.1",
-    "@types/node": "^12.12.37",
-    "@types/react": "^16.9.34",
-    "@types/react-dom": "^16.9.7",
     "react": "^16.13.1",
     "react-dom": "^16.13.1",
     "react-scripts": "3.4.1",
-    "typescript": "^3.7.5"
   },
+  "devDependencies": {
+    "@testing-library/jest-dom": "^4.2.4",
+    "@testing-library/react": "^9.5.0",
+    "@testing-library/user-event": "^7.2.1",
+    "@types/jest": "^24.9.1",
+    "@types/node": "^12.12.37",
+    "@types/react": "^16.9.34",
+    "@types/react-dom": "^16.9.7",
+    "typescript": "^3.7.5"
+ },

```

package.jsonを修正したら、installしなおします。

```bash
> rm -rf node_modules
> rm -f package-lock.json
> npm install
```

installしなおしたら念のため動作を確認しておきましょう。

```bash
> npm run start
```

## eslintの設定

次にeslintを導入して設定していきます。

今回Typescriptを使って開発していきますが、tslintは非推奨になり、eslintの方に移行していく流れがあるらしいのでeslintを利用します。
https://github.com/palantir/tslint/issues/4534


こちらを参考に導入していきます。
https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md

```bash
> npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
```

インストールできたら、`.eslintrc.js` と `.eslintignore` ファイルを作成します。

```js:.eslintrc.js
module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  plugins: [
    '@typescript-eslint',
  ],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'plugin:@typescript-eslint/recommended',
  ],
};
```

```config:.eslintignore
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
# don't lint nyc coverage output
coverage
```

package.jsonにlintのscriptを追加し、実行してみましょう。
App.tsxなどでlintのエラーが発生するはずなので、エラーが発生したらうまく導入できると思います。

```diff:package.json
   "scripts": {
     "start": "react-scripts start",
     "build": "react-scripts build",
     "test": "react-scripts test",
     "eject": "react-scripts eject",
+    "lint": "eslint . --ext .js,.ts,.tsx",
+    "lint:fix": "npm run lint -- --fix",
   },
```


```bash
> npm run lint
```

発生するエラー↓
![スクリーンショット 2020-04-29 15.10.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85232/a4cd71ab-1dd4-1fd8-5320-47b165307626.png)


ここまで確認できたら、lintのルールを拡張しましょう。
[eslint-config-airbnb-typescript](https://www.npmjs.com/package/eslint-config-airbnb-typescript) を利用すると楽っぽいので、それを使ってみます。

公式の手順を参考に導入していきます。
https://www.npmjs.com/package/eslint-config-airbnb-typescript

```bash
> npm install -D \
    eslint-config-airbnb-typescript \
    eslint-plugin-import@2.20.1 \
    eslint-plugin-jsx-a11y@6.2.3 \
    eslint-plugin-react@7.19.0 \
    eslint-plugin-react-hooks@2.5.0 \
    @typescript-eslint/eslint-plugin@2.24.0
```

インストールできたら、.eslintrc.jsを書き換えます。

```diff:.eslintrc.js
 module.exports = {
   root: true,
   parser: "@typescript-eslint/parser",
   plugins: [
     "@typescript-eslint"
   ],
   extends: [
-   'eslint:recommended',
-   'plugin:@typescript-eslint/eslint-recommended',
-   'plugin:@typescript-eslint/recommended',
+    "airbnb-typescript"
   ],
+  parserOptions: {
+    project: "./tsconfig.json",
+  },
 };
```

これで、reactやhooksまわりのlintのルール等が追加されました。

## formatter(Prettier)の導入

次にformatterとして[Prettier](https://prettier.io/)を導入していきます。

これも公式の手順を参考に導入していきます。
https://prettier.io/docs/en/install.html

```bash
> npm install -D --save-exact prettier
```

インストールできたら、prettierの設定ファイルを作成し、設定を記述します(この辺は好みで)。

```config:.prettierrc
{
  "tabWidth": 2,
  "useTabs": false,
  "singleQuote": true,
  "semi": true
}
```

package.jsonにformatを実行するscriptを追加して実行してみましょう。

```diff:package.json
   "scripts": {
     "start": "react-scripts start",
     "build": "react-scripts build",
     "test": "react-scripts test",
     "eject": "react-scripts eject",
     "lint": "eslint . --ext .js,.ts,.tsx",
+    "format": "prettier --write \"./**/*.ts\" \"./**/*.tsx\""
   },
```

```bash
> npm run format
```

実行すると、App.tsxなどのファイルがformatされているかと思います。

## eslintとPrettierの共存

eslintのfixを実行するとformatは実施されるので、prettierのformatと競合するケースがあります。
なので、 eslintのformat時に `.prettierrc` をみてもらうように設定し、prettierでのformatは実施しまないようにします。

まずはeslintのprettier puluginをインストールします。

```bash
> npm i -D eslint-plugin-prettier eslint-config-prettier
```

インストールできたら、.eslintrc.jsを下記のように修正します。

```diff:.eslintrc.js
 module.exports = {
   root: true,
   parser: "@typescript-eslint/parser",
   plugins: ["@typescript-eslint"],
   extends: [
     "airbnb-typescript",
+    "plugin:prettier/recommended",
+    "prettier/react"
   ],
   parserOptions: {
     project: "./tsconfig.json",
   },
 };
```

vscodeで[eslintのプラグイン](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)をインストールし、 `settings.json` で下記のように設定しておけば、ファイル保存時にformatを実施してくれるので便利です。

```json:settings.json
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
```

※eslintの設定と競合するprettierの設定は修正しておかないといけないです。
※package.jsonに追加したformatのscriptはもう使わないと思うので、削除しておくとよいでしょう。

## CSS in JS(Emotion)の導入

今回はStyleをあてていくツールとして、[Emotion](https://emotion.sh/docs/introduction)を利用しました。
emotionはCSS in JSになります。

styled-componentのようにも記述できますが、わざわざcomponentをwrapする書き方が煩しそうだったので、このemotionを利用しました。

ts側とstyle側で変数を共有できたり、styleの再利用が容易だったり、template側でクラス名を意識せずにすんだりと、とても開発しやすかったです。

普段Angularで開発してるんですが、Componentを作ることに関しては、ReactでtsxとCSS in JSを利用して作る方が楽しかったです。


これも公式の手順通りに導入していきます。
https://emotion.sh/docs/install

```bash
> npm install @emotion/core
```

インストールできたら、試しにstyleあててみます。

```diff:App.tsx
  import React from 'react';
+ /** @jsx jsx */
+ import { jsx, css } from '@emotion/core';
  import logo from './logo.svg';
  import './App.css';
 
  function App() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.tsx</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
+           css={css`
+             color: red;
+           `}
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
 
  export default App;

```

これで `Learn React` のテキストカラーが赤色になったかと思います。

## Reduxの導入

最後にReduxを導入していきます。

まず必要なpackageをinstallします。

```bash
> npm install react-redux redux
> npm install -D @types/react-redux
```

reducerでstateをイミュータブルに更新しやすくするために[immer](https://immerjs.github.io/immer/docs/introduction)も入れておきます。

```bash
> npm install immer
```

次に、store周りの実装を追加していきます。
今回はtodoのstoreを追加してみます。

```ts:src/store/todo/state.ts
export type Todo = {
  id: number;
  title: string;
  isDone: boolean;
};

export type State = {
  todos: Todo[];
};

export const initState: State = {
  todos: [],
};
```

```ts:src/store/todo/actions.ts
import { Action } from 'redux';
import { Todo } from './state';

export interface AddTodoAction extends Action {
  type: 'ADD_TODO';
  payload: { todo: Todo };
}

export const addTodo = (payload: AddTodoAction['payload']): AddTodoAction => ({
  type: 'ADD_TODO',
  payload,
});

export type Actions = AddTodoAction;

export const actionCreator = {
  addTodo,
};
```

```ts:src/store/todo/reducer.ts
import produce from 'immer';

import { Actions } from './actions';
import { State, initState } from './state';

export const reducer = (state: State = initState, action: Actions) => {
  switch (action.type) {
    case 'ADD_TODO':
      return produce(state, (draft) => {
        draft.todos.push(action.payload.todo);
      });
    default:
      return state;
  }
};
```

```ts:src/store/todo/index.ts
export * from './actions';
export * from './reducer';
export * from './state';
```

次にRootStoreを作成し、そこにtodoStoreを追加します。

```ts:src/store/root-store.ts
import { combineReducers, createStore } from 'redux';

import * as Todo from './todo';

export type RootState = {
  todo: Todo.State;
};

export const rootReducer = combineReducers({
  todo: Todo.reducer
});

export const actionCreator = {
  todo: Todo.actionCreator
};

export const store = createStore(rootReducer);
```

最終的なstoreのディレクトリ構成は下記のようになっていると思います。

```
src/store
├── root-store.ts
└── todo
    ├── actions.ts
    ├── index.ts
    ├── reducer.ts
    └── state.ts
```

これでstoreの実装ができたので、実際にComponentから使ってみます。

```diff:src/index.tsx
  import React from 'react';
  import ReactDOM from 'react-dom';
  import './index.css';
  import { Provider } from 'react-redux';
  import App from './App';
+ import * as serviceWorker from './serviceWorker';
+ import { store } from './store/root-store';
  
  ReactDOM.render(
    <React.StrictMode>
-     <App />
+     <Provider store={store}>
+       <App />
+     </Provider>
    </React.StrictMode>,
    document.getElementById('root')
  );
  
  // If you want your app to work offline and load faster, you can change
  // unregister() to register() below. Note this comes with some pitfalls.
  // Learn more about service workers: https://bit.ly/CRA-PWA
  serviceWorker.unregister();
```

最終的なApp.tsx ↓

```tsx:App.tsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
/** @jsx jsx */
import { jsx, css } from '@emotion/core';

import { RootState } from './store/root-store';
import { actionCreator } from './store/todo';

const App: React.FC = () => {
  const dispatch = useDispatch();
  const todos = useSelector((state: RootState) => state.todo.todos);
  return (
    <div className="App">
      <button
        type="button"
        onClick={() =>
          dispatch(
            actionCreator.addTodo({
              todo: {
                id: 0,
                title: '牛乳買う',
                isDone: false,
              },
            })
          )
        }
      >
        Add todo
      </button>
      {todos.map((v) => (
        <p
          css={css`
            margin-top: 8px;
          `}
        >
          {v.title}
        </p>
      ))}
    </div>
  );
};

export default App;
```

画面に表示された `Add todo` ボタンを押下すと、追加されたtodoのタイトルが画面に表示されると思います。

これでアプリケーションにReduxの導入ができたので、これをベースに拡張していけばよいです。


## 最後に

これでReactで開発を進めていく準備が整いました。
あとはがりがり実装していくだけです！

そしてがりがり実装して作ったものを再掲しておきます。
https://tasktrack.web.app
https://note.com/digitalshuufei/n/nf7e9d9644d13

今回のソースコードはこちら。
https://github.com/shuufei/react-project-setup


## 参考
https://github.com/facebook/create-react-app#creating-an-app
https://github.com/palantir/tslint/issues/4534
https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md
https://www.npmjs.com/package/eslint-config-airbnb-typescript
https://prettier.io/docs/en/install.html
https://emotion.sh/docs/install
https://react-redux.js.org/introduction/quick-start