ブラックボックスにならないReact導入

# 概要
[React公式サイトのチュートリアル](https://ja.reactjs.org/docs/create-a-new-react-app.html)では、React入門者に対して`create-react-app`コマンドによりReactのプロジェクトを作成することを勧めています。このコマンドを実行することで簡単にReactの実行環境が整います。しかし、このコマンドの裏では沢山の処理が動いており、Reactやフロントエンド初心者にとって、Reactの内部がブラックボックスになる原因になっていると思います。
そこで、この記事では`create-react-app`コマンドを使わずに`create-react-app`を使うのと変わらないような開発環境を構築することを目指します。

# 事前準備

### Node.JSのインストール

`npm`コマンドが使えるようにNode.JSをインストールして下さい。Node.JSのインストールは良い解説が他に沢山あるので、この記事では解説しません。

### npmのプロジェクトの作成

これから作業を行うnpmのプロジェクトを作成します。
`npm init`コマンドを使うことで、カレントディレクトリをnpmのプロジェクトにすることができます。ディレクトリの名前は何でも構いません。
例では`my-react-sample`という名前にしています。

```bash:コンソール
mkdir my-react-sample

cd my-react-sample

npm init
```

# webpackの導入と動作確認

以下の手順では[webpackの公式チュートリアル](https://webpack.js.org/guides/getting-started/)を参考にwebpackの導入を進めます。
`webpack`とはJavaScript言語用の依存関係解決ツールのようなものです。Javaでの`gradle/groovy`やC++での`CMake`に近いと思います。
基本的にReactはwebpackとセットで使われます。

### webpackのインストール

`webpack`と`webpack-cli`をインストールします。

```bash:コンソール
npm install --save-dev webpack webpack-cli
```

### ファイルの作成

webpackの動作確認のために以下のように`index.html`と`index.js`とディレクトリを作成します。

```diff
  myReactSample
  |- package.json
+ |- /dist
+   |- index.html
+ |- /src
+   |- index.js
```
このような階層になるように作ります。

### index.htmlの内容を作成

`index.html`を以下のようにしてください。

```html:dist/index.html
<!doctype html>
<html>
  <head>
    <title>Getting Started</title>
  </head>
  <body>
    Hello!
  </body>
</html>

```

`index.html`ファイルをブラウザで開くと、当然`Hello!`と表示されます。

### index.jsの内容を作成

index.jsを以下のようにしてください。

```javascript:src/index.js
function component() {
  const element = document.createElement('div');
  element.innerHTML = "Webpack!"
  return element;
}

document.body.appendChild(component());

```
`body`に`<div>Webpack!</div>`を追加しているだけですね。

### index.htmlからmain.jsを読み込む

main.jsを読み込むために、index.htmlに以下のように`<script src="main.js"></script>`を追加しています。

```html:dist/index.html
<!doctype html>
<html>
  <head>
    <title>Getting Started</title>
  </head>
  <body>
    Hello!
    <script src="main.js"></script>
  </body>
</html>

```

webpackではデフォルトで`src/index.js`を入力として`dist/main.js`に依存関係が解決されたJavaScriptファイルを出力します。これをwebpackによるビルドと言います。
`dist/index.html`からビルド成果物である`dist/main.js`を読み込むことでプログラムを動かすことが出来ます。
`index.html`と`main.js`の両方とも`dist`ディレクトリ内であるため、相対パスで`main.js`を指定して読み込めばよいです。

### webpackでのビルド

以下のコマンドを打ち込むと、`src/index.js`が`dist/main.js`にビルドされます。

```bash:コンソール
npx webpack
```

```diff
  myReactSample
 |- package.json
 |- /dist
   |- index.html
+  |- main.js
 |- /src
   |- index.js
```
このような階層になっているはずです。この状態で`index.html`をブラウザで開くと`Hello!`の下に`Webpack!`が表示されるはずです。

正しく表示されたなら、Webpackの導入は成功です。おめでとうございます！次にWebpack DevServerの導入を行います。

# Webpack DevServerの導入と動作確認

Webpack DevServerとは、プロントエンド開発用に開発PC上で立ち上げる、ローカルのHTTPサーバーです。JavaScriptファイルを更新した際に自動でWebPackのビルドの実行とページの更新を行ってくれるので非常に便利です。ちなみに、`create-react-app`コマンドを使った際は自動で入ります。

### Webpack DevServerのインストール

以下のコマンドでWebpack DevServerをインストールします。

```bash:コンソール
npm install --save-dev webpack-dev-server
```

プロジェクトのルートに`webpack.config.js`を作ります。

```diff
  myReactSample
 |- package.json
+|- webpack.config.js
 |- /dist
   |- index.html
   |- main.js
 |- /src
   |- index.js
```

`webpack.config.js`を以下のように編集します。

```javascript:webpack.config.js
var path = require('path');

module.exports = {
  mode: 'development',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000
  }
};
```

これはwebpackの設定ファイルです。
まず、webpackのビルドを`development`モードに指定しています。
また、`devServer`内でWebpack DevServerがホストするコンテンツのディレクトリの指定やportの指定などをしています。

### Webpack DevServerの動作確認

コンソールでWebpack DevServerを起動します。

```bash:コンソール
npx webpack-dev-server
```

localホストでサーバーが起動します。`9000`番ポートを指定してブラウザで開いて下さい。
うまく行かない場合は`webpack.config.js`でコロンが足りないなどでエラーになっている事があります。

正しく設定が出来ると、`index.js`を編集して更新した時に自動でリビルドとページの更新処理が行われます。

### コンテンツ監視の設定
デフォルトの設定では、JavaScriptファイルを更新した時のみページの更新が行われます。HTMLファイルやCSSファイルの更新時にもページの更新が行われるようにするために、コンテンツ監視の設定をします。
以下のように`devServer:`の中に`watchContentBase: true`を追記してください。

```javascript:webpack.config.js
var path = require('path');

module.exports = {
  mode: 'development',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000,
    watchContentBase: true
  }
};

```

追記した後、コンソールでWebpack DevServerを再起動します。`Ctrl + c`してから、以下のコマンドを実行して起動します。

```bash:コンソール
npx webpack-dev-server
```

すると、jsファイルだけでなく、htmlファイルを更新した際も自動でページの更新が行われるようになります。

ここまで上手く行けばWebpack DevServerの導入は成功です！次にbabelの導入を行います。

# babelの導入

### babelのインストール

Reactでは[JSX](https://ja.reactjs.org/docs/introducing-jsx.html)という特殊なJavaScript拡張構文を使います。JSXを使えるようにするためにJSトランスパイラであるbabelを導入します。

```bash:コンソール
npm install --save-dev @babel/core babel-loader @babel/polyfill @babel/preset-env @babel/preset-react 
```

### webpack.configの変更

babelの利用をwebpack.configファイルの`rules`内で指定します。

```javascript:webpack.config.js

var path = require('path');

module.exports = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: [
                "@babel/preset-env",
                "@babel/react"
              ]
            }
          }
        ]
      }
    ]
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000,
    watchContentBase: true
  }
};

```

この状態で`npx webpack`コマンドを使ってビルドすることが出来れば、babelの導入は成功です！

# Reactの導入

いよいよReactを導入します！ReactはJavaScriptのライブラリとして提供されています。まずはインストールします。

### Reactのインストール

Reactは`development`環境だけでなく`production`環境でも必要であるため、`--save-dev`オプションではなく`--save`オプションを適用します。

```bash:コンソール
npm install --save react react-dom
```

### Reactを使う！

Reactは指定したidを持つDOM要素とその子要素を支配するという形で動作します。今回は`root`というid名を指定することにします。
index.htmlに`<div id="root"></div>`を追加します。

```html:dist/index.html
<!doctype html>
<html>
  <head>
    <title>Getting Started</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="main.js"></script>
  </body>
</html>

```

index.jsでも同じidを指定します。ファイルの最後の方にある、ReactDOM.render内で指定しています。

```jsx:dist/index.js
import React from 'react'
import ReactDOM from 'react-dom'

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    )
  }
}

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  )
}

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

```

このコードはReactの公式チュートリアルの[stateとライフサイクル](https://ja.reactjs.org/docs/state-and-lifecycle.html)で紹介されているものです。時刻が更新され続ける文字が表示されたらReactの導入は成功です！お疲れ様でした！

# TypeScriptの導入

静的言語で開発したくなる時があるのでTypeScriptでReactを使えるようにします。

### TypeScriptのインストール

```bash:コンソール
npm install --save-dev typescript ts-loader
```

### tsconfigの作成

TypeScriptの設定ファイルである`tsconfig.json`ファイルを作成します。

```diff
  myReactSample
 |- package.json
 |- webpack.config.js
+|- tsconfig.json
 |- /dist
   |- index.html
   |- main.js
 |- /src
   |- index.js
```

以下のように編集します。

```json:tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5",
    "module": "es2015",
    "jsx":"react"
  }
}
```

### webpack.configの編集

TypeScriptトランスパイラの利用などをwebpack.configファイルで指定します。

```javascript:webpack.config.js
var path = require('path');

module.exports = {
  mode: 'development',
  entry: "./src/index.tsx",
  module: {
    rules: [
      {
        test: /\.tsx$/,
        use: "ts-loader"
      }
    ]
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"]
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000,
    watchContentBase: true
  }
};
```

### index.jsのリネーム

拡張子を".tsx"とすることで、TypeScriptのトランスパイラがJSX構文を扱うことが出来るようになります。

```diff
  myReactSample
 |- package.json
 |- webpack.config.js
 |- tsconfig.json
 |- /dist
   |- index.html
   |- main.jsx
 |- /src
+  |- index.tsx
-  |- index.js
```

### 型定義ファイルのインストール

reactとreact-domをTypeScriptから使うために、それぞれの型定義ファイルをインストールします。

```bash:コンソール
npm install --save-dev @types/react @types/react-dom
```

### JavaScriptファイルをTypeScriptファイルに書き換える

- import文の書き換え
- `Props`, `State`インターフェースの定義
- `Clock`クラスが継承するクラスを`React.Component`から`React.Component<Props, State>`へ変更

などを行っています。

```tsx:index.jsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

interface Props {
}

interface State {
  date: Date
}

class Clock extends React.Component<Props, State> {
  timerID: any
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    )
  }
}

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  )
}

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

```

### webpackビルド

```bash:コンソール
npx webpack
```

ビルドでエラーが出る場合はエラーメッセージを元に修正して下さい。

### Webpack DevServerでのホスト

ビルドエラーが出なくなったらWebpack Devserverを起動します。

```bash:コンソール
npx webpack-dev-server
```

TypeScript導入前と同じように、時刻が更新され続ける文字が表示されたらTypeScriptの導入は成功です！お疲れ様でした！

# 導入完了！

以上でReact、Webpack、Webpack DevServer、Typescriptの導入が完了しました！モダンなWeb開発はブラックボックスになりがちですが、一つずつ順番に導入することで裏側で何が動いているのか少しは明らかに出来たのではないかと思います！

Reactの初心者の方は、この後Reactの公式チュートリアルを進めてReactに慣れると良い思います。

- [Reactチュートリアル : 概要](https://ja.reactjs.org/tutorial/tutorial.html#overview)

既にある程度Reactに詳しい人は、WebpackやTypescriptのconfigファイルの設定一覧を見てみると良いと思います。今回の記事ではシンプルな設定にしましたが、設定できる便利な機能が沢山あります。

- [Configuration | webpack](https://webpack.js.org/configuration/)
- [tsconfig.json | TypeScript](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)