LoginSignup
52
61

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-20

はじめに

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

現在の状況

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

参考図書

このチャットは以下の本を参考にしながら作成しました。
いまどきのJSプログラマーのための Node.jsとReactアプリケーション開発テクニック

環境設定

ファイル構成

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

|
|ーー 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
参考資料: 手順を分かりやすく解説!Node.jsのインストール方法 | TechAcademyマガジン

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

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

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

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

package.jsonの設定

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

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です。

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

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として管理するための設定を行っています。

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で最適化が行われる際に、このファイルが読み込まれるのです。

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.jsonscriptsbuild: startと定義しているため、このコマンドを打つとwebpackによってsrc/index.jsが最適化されて、public/bundle.jsに出力されます。

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

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

まとめ

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

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

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

参考資料

webpack.config.jsのdevtoolsについて- React.jsでビルドされる前のコードをブラウザで確認する
webpack.config.jsのmoduleについて- webpackチートシート
server.jsrequire('http')について - node.jsでrequire('http');する理由

追加した機能

Firebaseでログイン&ユーザー名使用
FirebaseRealtimeDatabaseにデータの追加、削除

52
61
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
52
61