Reactでのチャット作成手順を誰にでもわかるように解説する

## はじめに
勉強していた`React`をどのくらい理解しているのか、どんな知識が不足しているのかを知るためにチャットシステムの構築に挑戦しました。
丁寧な解説を心がけて記事を執筆していきます。
わからない点、理解しづらい所があればコメントでの質問をお願いします。
指摘も大歓迎です。

## 現在の状況
ReduxやFirebaseといった機能を追加しようとしているため、十分に稼働していない可能性があります。
参考程度に捉えるのがオススメです。

- URL: [Reactチャット](https://react-chat-b0e8a.firebaseapp.com/)

- Github: [kaibara/React-chat](https://github.com/kaibara/React-chat)


## 参考図書
このチャットは以下の本を参考にしながら作成しました。
[いまどきのJSプログラマーのための Node.jsとReactアプリケーション開発テクニック](https://www.amazon.co.jp/%E3%81%84%E3%81%BE%E3%81%A9%E3%81%8D%E3%81%AEJS%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE-Node-js%E3%81%A8React%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA%E3%83%86%E3%82%AF%E3%83%8B%E3%83%83%E3%82%AF-%E3%82%AF%E3%82%B8%E3%83%A9%E9%A3%9B%E8%A1%8C%E6%9C%BA/dp/4802611145/ref=sr_1_1?ie=UTF8&qid=1550649460&sr=8-1&keywords=%E3%81%84%E3%81%BE%E3%81%A9%E3%81%8D%E3%81%AEJS)

## 環境設定
### ファイル構成
このチャットは以下のファイル構成で稼働しています。

```sh
|
|ーー src  //作業用ディレクトリ
|　　 |ーー index.js　//componentsでのファイル、componentがここにまとめられる
|　　 |ーー components
|　　 　　 |ーー Form.js　//チャットのFormを作成するファイル
|　　 　　 |ーー App.js　 //チャットのlogを管理するファイル
|
|ーー public　//出力用ディレクトリ
|　　 |ーー index.html  //bundle.jsを読み込み、サーバーに読み込まれるファイル
|　　 |ーー bundle.js   //srcのファイルがここにまとめられ、index.htmlに適用される
|
|ーー node_module　　　　　 //このプロジェクトで使用されるmoduleがまとめられているディレクトリ(操作しない)
|ーー server.js           //Node.jsにおけるサーバーの設定をするファイル
|ーー package.json　　　　　//このプロジェクトを管理するためのファイル
|ーー package-lock.json　　//package.jsonの状態を保存するファイル(操作しない)
|ーー webpack.configjson　 //webpackで最適化する際の設定が記載されたファイル
|ーー README.md　　　　　　　//Githubのリポジトリにアクセスした際、そのリポジトリの説明などを行うファイル
|ーー .gitgnore            //端末で操作するgitのコマンドなどを管理するファイル(基本的に操作しない)

```

### Github
まずはGithubで新しいリポジトリを作成しましょう。
ファイルの状態を管理してくれるだけじゃなく、以前の状態にファイルを戻すことができるため、かなり重宝します。
リポジトリを作成したら次へ進んでください。

### moduleのインストール
今回はユーザー同士がやり取りできるよう、サーバーが必要です。
`JavaScript`だけでサーバーを動かせるように、`Node.js`を使用します。
また、多くのmoduleを使用、管理するために`npm`も利用します。

`Node.js`をインストールすれば`npm`もインストールされるため、次のURLからそれぞれのOSに合った方法でインストールしてください。
[Download | Node.js](https://nodejs.org/en/download/)
参考資料: [手順を分かりやすく解説！Node.jsのインストール方法 | TechAcademyマガジン](https://techacademy.jp/magazine/16050)

`Node.js`、`npm`がインストールできたら、Githubからリポジトリをコピーし、次のように`npm`からモジュールをインストールしましょう。

```console
 $ git clone {Githubで作成したリポジトリのSSH}
 // 作成したリポジトリをコピーしてローカルで操作できるようにする。
 $ cd {Githubで作成したリポジトリの名前}
 $ git checkout -b {ブランチ名}
 $ npm init
 //それぞれの内容に合わせてキーを打つ or Enter
 //その内容に合わせてnpmがプロジェクトを管理するpackage.jsonを作成される。
 
 $ npm install --save express socketio socket.io-client react react-dom
 //開発、実行時共に必要なmodule
 
 $ npm install --save-dev webpack webpack-cli babel-loader babel-core babel-preset-es2015 babel-preset-react
 //開発時のみに必要なmodule
```

#### 各moduleの解説

- express
　`Node.js`で使っている人が最も多いWebフレームワークです。
　API(アプリケーションプログラミングインターフェース)があることで、サーバーとのデータの送受信が可能となります。

- socketio,socket.io-client
　サーバーとクライアントの双方向通信を効率的に行うWebSocketを実装するためのmoduleです。 

- react,react-dom
　`react`でアプリケーションを作成するファイルは`react`を`import`する必要があります。
　`react-dom`によってhtmlなどの特定の部分に`react`のプログラムを適用させることができるようになるのです。

- webpack,webpack-cli 
　 作成したファイルを`js`や`html`など言語で分けた後にまとめて、最適化してくれます。

- babel-loader,babel-core,babel-preset-es2015,babel-preset-react
　開発者が使用している言語とブラウザーで使用される言語は同じであってもバージョンが異なることがあります。
　そのままWeb上に公開してしまっては、最新のバージョンでしか使用できないコードがあった場合、ブラウザーではコードを認識できません。
　それを防ぐためのトランスパイラと呼ばれるツール達がこのmoduleです。

### package.jsonの設定
`npm`でモジュールのインストールが完了したら、プロジェクトの管理をする`package.json`を確認してください。

```js:package.json
{
  "name": "***",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  //$ npm init で指定した内容が入力される
  
  "scripts": {
    "start": "node server.js",
    //server.jsの内容に基づいてnode.jsのサーバーが起動する
    "build": "webpack",
    //webpackによって読み込んだファイルを最適化する
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
   //$ npm install --save でインストールした開発、実行で使用するmodule達
  },
  "devDependencies": {
   //$ npm install --save-dev でインストールした開発のみで使用するmodule達
  }
}
```

ここでは`"scripts"`を見てください。
そこに書かれている左側の単語を`$ npm (左側の単語)`と打ち込むことで、右側のコマンドが実行されます。

### webpack.config.jsの設定
上にもある`$ npm run build`、`$ webpack`が行われる際、このファイルを元に作業したファイルの最適化が行われるのです。
どのような内容があるかをコメントアウトで説明していきます。

```webpack.config.js
const path = require('path')
module.exports = {
  entry: path.join(__dirname, 'src/index.js'),
  //root/src/index.jsを読み込む
  output: {
    path: path.join(__dirname, 'public'),
    filename: 'bundle.js'
  },
  //root/public/ にbundle.jsとしてentryで読み込んだファイルを最適化し出力する
  devtool: 'inline-source-map',
  //chromeの開発者ツールで最適化されるファイル達をその前の状態で確認できるようにする
  module: {
    rules: [
      {
        test: /.js$/,
        loader: 'babel-loader',
        options: {
          presets: ['es2015','react']
        }
      }
    ]
  }
  //babelなどトランスパイルをするmoduleを最適化する際のルールの設定
}

```

## サーバーの設定
このチャットでは、httpサーバーとWebsocketサーバーが存在しています。
流れを簡潔に解説します。

ユーザーがブラウザで指定されたURLに接続した時にhttpサーバと接続します。
その後、httpサーバーからチャットを構成するファイルやcomponentが渡されるのです。
それと同時にWebSocketのクライアントアプリがユーザーに引き渡されます。
そうすることで、WebSocketによる双方向通信が可能となるのです。

では、`Node.js`で起動するサーバーの設定を行う`server.js`を作成していきましょう。

```server.js
const express = require('express')
const app = express()
const server = require('http').createServer(app)
//httpサーバーの作成
//const server = createServer(app)でもサーバーは作成できるが、expressのサーバーを動かすとなると多くのmoduleが必要になる。
//httpだけであれば必要最低限の構成でサーバーを起動できる

const portNumber = 3005
//番号はなんでも良いが、他で使用している番号と重ならないように注意

server.listen(portNumber, () => {
  console.log('起動しました', 'http://localhost' + portNumber)
})
//サーバーの起動時、端末にポート番号を表示する。
//そうすることで、コピー＆ペーストによって簡単にブラウザからチャットに接続できる。

app.use('/public', express.static('./public'))
//クライアントにアクセスさせたい情報をapp.useに格納している
app.get('/',(req,res) => {
  res.redirect(302, '/public')
})
//クライアントからの要求があったときに一時的にpublicの内容を渡す

const socketio = require('socket.io')
const io = socketio.listen(server)
//WebSocketサーバーの設定

io.on('connection',(socket) => {
//ユーザーが接続してきた時の処理
  console.log('Acces to User:', socket.client.id)
  socket.on('chatMessage',(msg) => {
    //メッセージ受信時の処理
    console.log('message',msg)
    io.emit('chatMessage',msg)
    //全てのユーザーにメッセージを送信
  })
})
```

## React編
続いて、チャットを構成するcomponentを解説していきます。

### chatForm.js
このチャットでは`名前`と`メッセージ`を送信できるようにします。
それぞれのフォームを構成しているのがこの`Form.js`です。

各タグの`id`や`className`は`css`を適用させやすいようにしているだけです。
省略しても構いません。

```jsx:Form.js
import React,{Component} from 'react'
import socketio from 'socket.io-client'

const socket = socketio.connect('http://localhost:3005')

class Form extends Component {
  constructor(props){
    super(props)
    this.state = {
      name: '',
      message: ''
      //このcomponentで扱うnameとmessageの初期値を設定する。
    }
  }

  nameChanged(e){
    this.setState({name: e.target.value})
  }
  //このイベントの発生時、this.state.nameにvalueの値が入る
  messageChanged(e){
    this.setState({message: e.target.value})
  }
  //このイベントの発生時、this.state.messageにvalueの値が入る
  send(){
    socket.emit('chatMessage',{
      name: this.state.name,
      message: this.state.message
    })
    this.setState({message: ''})
  }
  //このイベント発生時、socket.io-clientがlocahostにnameとmessageの値が入ったchatMessageを全てのユーザーに送信する。
  //その後、messageの値だけを初期値に戻す。

  render(){
    return(
      <div id='Form'>
        <div className='Name'>
          名前:
          <br />
          <input value={this.state.name} onChange={e => this.nameChanged(e)} />
　　　　　　//名前フォーム内に何か打ち込まれたらthis.state.nameに打ち込まれたものが入り、this.nameChangedイベントが発生する
        </div>
        <br />
        <div className='Message'>
          メッセージ:
          <br />
          <input value={this.state.message} onChange={e => this.messageChanged(e)} />
　　　　　　//メッセージフォーム内に何か打ち込まれたらthis.state.nameに打ち込まれたものが入り、this.messageChangedイベントが発生する
        </div>
        <button className='send' onClick={e => this.send()}>送信</button>
　　　　　//ボタンを押されたらsendが発生する
      </div>
    )
  }
}

export default Form
```

### App.js
このcomponentでは`Form.js`から送られた値をlogとして管理するための設定を行っています。

```jsx:App.js
import React,{Component} from 'react'
import socketio from 'socket.io-client'
import Form from './Form'

const socket = socketio.connect('http://localhost:3005')

class App  extends Component {
  constructor(props){
    super(props)
    this.state = {
      logs: []
    }
    //このcomponentで扱う配列logsの初期値を設定する
  }
  componentDidMount(){
  //このコンポーネントがDOMによって読み込まれた後の処理を設定する
    socket.on('chatMessage',(obj) => {
      //WebSocketサーバーからchatMessageを受け取った際の処理
      const logs2 = this.state.logs
      //logs2に今までのlogを格納する
      obj.key = 'key_' + (this.state.logs.length + 1)
      //メッセージ毎に独自のキーを設定して判別できるようにする
      console.log(obj)
      //consolelogにobj.key、name、messageを表示する
      logs2.unshift(obj)
      //配列の一番最初に最新のメッセージを入れる。
      //そうすることで新しいメッセージほど上に表示されるようになる
      this.setState({logs: logs2})
      //最新のkey、name、messageが入ったlogs2をlogsに入れる。
    })
  }

  render(){
    const messages = this.state.logs.map(e => (
      <div key={e.key}>
        <span>{e.name}</span>
        <span>: {e.message}</span>
        <p />
      </div>
    ))
    //ログの設定。今までのname、messageをkeyごとに表示する
    return(
      <div>
        <h1 id='title'>Reactチャット</h1>
        <ChatForm />
        <div id='log'>{messages}</div>
      </div>
    )
  }
}

export default App
```

### index.js
`App.js`を読み込み、DOMに書き込みます。
そして、webpackで最適化が行われる際に、このファイルが読み込まれるのです。

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

ReactDOM.render(
  <ChatApp />,
  document.getElementById('root')
)
```

## 起動編
### public/index.html
`src/`ディレクトリでチャットの設定が完了したら、`public/index.html`のファイルを編集します。

```public/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Rectチャット</title>
    /*chromeなどのタブに表示する名前の設定*/
  </head>
  <body>
    <div id="root"></div>
    <script src="/public/bundle.js"></script>
    /*webapckによって作成されたbundle.jsが読み込まれる。*/
　　/*つまり、src/以下のファイルで書いたcomponentが反映される*/
  </body>
</html>
```

### npmでサーバーを起動する
以上で、ファイルにコードを書くのは終了です。
あとは、端末での操作を行い、サーバーを起動しましょう。
行う操作は次の2つです。

1つ目は`$ npm run build`
`package.json`の`scripts`で`build: start`と定義しているため、このコマンドを打つと`webpack`によって`src/index.js`が最適化されて、`public/bundle.js`に出力されます。

2つ目は`$ npm start`
1つ目と同様、`package.json`の`scripts`で`start: node server.js`と定義しているため、このコマンドを打つと`Node.js`が`server.js`を読み込み、その設定基づいてサーバーを起動します。

端末を開くと`起動しました: http://localhost:3005`が表示されます。
ブラウザでそのURLに接続すると、チャットが表示されます。
もし、表示されていない場合はエラーが発生しているため、適宜修正していってください。

## まとめ
以上、Reactでの作成手順を解説しました。

このチャットでは更新するたびにログが削除されます。
更新しても今までのログを表示するには、jQueryなどでバックエンドにデータを保管するといった方法が考えられます。

この記事を元にチャットの型、フロント部分を作成し、後は自分で作りたいチャットにできるよう手を加えたり、修正されたりしてみてはいかがでしょうか。

## 参考資料
`webpack.config.js`のdevtoolsについて- [React.jsでビルドされる前のコードをブラウザで確認する](https://qiita.com/keng0o/items/fa7a17cf22dbc7d71e74)
`webpack.config.js`のmoduleについて- [webpackチートシート](https://qiita.com/morrr/items/0f35adc38b59f5a67141)
`server.js` の`require('http')`について　- [node.jsでrequire('http');する理由](https://teratail.com/questions/8894)

## 追加した機能
[Firebaseでログイン&ユーザー名使用](https://qiita.com/kaitaku/items/2b30f86c9241dc597bf9)
[FirebaseRealtimeDatabaseにデータの追加、削除](https://qiita.com/Takumi_Kaibara/items/f77fc899b3f76db01c23)