🐕

Node.js+ReactでSocket.ioを使う

2022/02/28に公開

備忘録的にNode.jsとReactを連携させてSocket.ioを使う手順をまとめとこうと思います。

Reactの環境構築

まずは以下のコマンドでReactアプリを開発するための雛形を作ります。

npx create-react-app client --template typescript

そして、作った雛形にcdコマンドで移動して以下のコマンドで使用するパッケージをインストールします。

npm i -d socket.io-client @types/node

次にtsconfig.jsonを以下のような内容にします。

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

次に設定ファイルを作成します。
srcディレクトリの中に「config」というディレクトリを作成し「default.ts」というファイルを作成します。以下のような内容にします。

export const SOCKET_URL = process.env.SOCKET_URL || "http://localhost:4000";

バックエンド側のURLを取ってきています。

次にsocket.ioでサーバー側から送られてきた値を全コンポーネントで扱うためにcontextを作成します。
srcディレクトリに「context」というディレクトリを作成し、「socket.context.tsx」というファイルを作成します。以下のような内容にします。

import { useContext, createContext, useState } from 'react';
import io, { Socket } from 'socket.io-client';
import { SOCKET_URL } from './config/default';
import EVENTS from "./config/events";

interface Context {
    socket: Socket,
    setUsername: Function
    messages?: {message: string, username: string, time: string}[],
    setMessages: Function
}

//SOCKET_URLの中身のところに接続を要求
const socket = io(SOCKET_URL);

const SocketContext = createContext<Context>({
    socket, 
    setUsername: () => false ,
    setMessages: () => false
});

function SocketsProvier(props: any) {
    const [messages, setMessages] = useState([]);

    return (
        <SocketContext.Provider value={{ socket, messages, setMessages }} {...props} />
    );
}

export const useSockets = () => useContext(SocketContext);

export default SocketsProvier;

次にindex.tsxの中身を以下のようにしてrenderの中身をproviderで囲みます。

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import SocketsProvider from './context/socket.context'

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

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

これでsocketを全コンポーネントで使えるようになりました。

次にsocketのイベントを追加するのにApp.tsxを次のように編集します。

import React, { useRef, useState } from 'react';
import logo from './logo.svg';
import './App.css';
import {useSockets} from './context/socket.context';

function App() {
  const {socket, messages, setMessages} = useSockets();
  const messageRef = useRef(null);
  console.log(messages);

  function handleClick() {
    const message = messageRef.current.value;
    if (!String(message).trim()) return;

    socket.emit("sendMessage", message);

    messageRef.current.value = "";
  }

  socket.on("responseMessage", (message) => {
    console.log(message);
    setMessages([...messages, message]);
    console.log(messages);
  });

  return (
    <>
      <input type="text" ref={messageRef} placeholder="write message" />
      <button onClick={handleClick}>Send</button>
      <Messages />
        
    </>
  );
}

function Messages() {
  const {socket, messages, setMessages} = useSockets();
  return (<>
    {messages && (<div>
      {messages.map(({message}, index) => {
        return <li key={index}>{message}</li>
      })}
    </div>)}
  </>
  );
}

export default App;

バックエンドの構築

次にルートディレクトリにserverディレクトリを作成します。
serverディレクトリに移動し、「npm init」をした後以下のコマンドで必要パッケージをインストールします。

npm i -d typescript ts-node socket.io express config @types/node @types/express @types/express @types/config

configディレクトリを作成し、default.tsを作成し以下のように記述します。

export default {
    corsOrigin: "http://localhost:3000",
    port: 4000,
    host: "localhost",
};

クロスオリジン先のurlと自分のホスト、ポート番号を設定しています。

また、tsconfig.jsonを以下のように設定しておきます。

{
    "compilerOptions": {
      "resolveJsonModule": true,
      "target": "es2016",
      "module": "commonjs",
      "esModuleInterop": true,
      "forceConsistentCasingInFileNames": true,
      "strict": true,
    }
}

次にsrcディレクトリを作成し、app.tsファイルを作成します。内容は以下の通りです。

import express from "express";
import { createServer } from "http";
import { Server } from "socket.io";
import config from "config";

import socket from "./socket";

const port = config.get<number>("port");
const host = config.get<string>("host");
const corsOrigin = config.get<string>("corsOrigin");

const app = express();

const httpServer = createServer(app);

const io = new Server(httpServer, {
    cors: {
        origin: corsOrigin,
        credentials: true
    },
});

app.get('/', (_, res) => res.send(`Server is up`));

httpServer.listen(port, host, () =>{  
    console.log(`http://${host}/${port}`);   

    socket({ io }); 
});

srcディレクトリにsocket.tsというファイルを作り以下の内容にします。

import { Server, Socket } from "socket.io";

function socket({io}: {io: Server}){

    io.on("connection", (socket: Socket) => {
        console.log(`User connected ${socket.id}`);

        socket.on("sendMessage", (message) => {
            socket.emit("responseMessage", message);
        });
    });
}

export default socket;

以上

Discussion