Node.js+ReactでSocket.ioを使う
備忘録的に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