- 오늘은 socket.io를 ejs와 react로 정리하는 실습을 진행했다
- useEffect를 사용하여 socket을 연결하는 부분이 이해하기 어려웠다
- react를 통한 socket설정부터 사용까지 쭉 정리해보기
- npx create-react-app으로 client라는 이름의 폴더를 생성
- 소켓을 위한 모듈: socket.io-client설치 + css관련 모듈도 설치함
- server폴더 생성하고 npm init -y
- 모듈: socket.io, express, cors(react로 view만드려면 필수)
- app.js파일에서 모듈 불러오고 http기반 server생성
- sockets폴더 생성하여 index.js에서 socket코드 따로 관리
- socketIO로 모듈 require하고 socketHandler함수를 만들어 exports하여 app.js에서 http server를 인자로 받아 사용함
const express = require("express");
const http = require("http");
const PORT = 8080;
const app = express();
const server = http.createServer(app);
const socketHandler = require("./sockets");
socketHandler(server);
//cors: 다른 서버에 보내는 요청 제한
const cors = require("cors");
app.use(cors());
server.listen(PORT, () => {
console.log(`http://localhost:${PORT}`);
});
function socketHandler(server) {
const io = socketIO(server, {
cors: {
origin: "http://localhost:3000",
},
});
io.on("connection", (socket) => {
//여기서 socket을 통한 작업 진행
});
}
module.exports = socketHandler;
- app.js가 아니라 각 컴포넌트에서 선택적으로 socket을 사용할 것이기 때문에 컴포넌트에서 socket연결 코드를 작성함
- client에서 다운로드한 socket.io-client모듈을 require하여 io를 가져온다
- io.connect로 로컬호스트 주소와 연결하고, autoConnect는 false로 설정한 socket객체를 생성한다
- socket연결이 false상태이므로 mount되면 연결이 진행되도록 연결함수 생성 후 useEffect의 mount시 불러오도록 한다
- 연결이 완료되면 컴포넌트에서 이전과 동일한 방식(on과 emit으로 사용가능!)
import { useEffect } from "react";
import io from "socket.io-client";
const socket = io.connect("http://localhost:8080", { autoConnect: false });
export default function Start() {
const initSocketConnect = () => {
console.log(socket.connected);
if (!socket.connected) socket.connect();
};
useEffect(() => {
initSocketConnect();
socket.emit("test", "테스트");
socket.on("test2", (msg) => {
console.log(msg);
});
}, []);
return <p>소켓 연결 테스트 입니다</p>;
}
- 메시지 전송기능, 닉네임표시, 입장,퇴장 알림, dm기능을 지원하는 채팅페이지
const [msgInput, setMsgInput] = useState(""); //submit시 메시지 전송데이터
const [chatList, setChatList] = useState([]); //전체 채팅데이터
const [nicknameInput, setNicknameInput] = useState(""); //닉네임 input창
const [nickname, setNickname] = useState(null); // 내 닉네임을 관리하는 state
const [userList, setUserList] = useState({}); //전체 클라이언트 닉네임 정보 관리
const [dmTo, setDmTo] = useState("all"); //dm여부 관련 state
- 1) mount[]: 닉네임 중복 검사관련 error, entrySuccess, updateNicks이벤트 사용됨
useEffect(() => {
//닉네임 사용3: 입장실패
socket.on("error", (errMsg) => {
alert(errMsg);
});
//닉네임 사용3: 입장성공 - 내 닉네임 전달받아 state에 저장
socket.on("entrySuccess", (nick) => {
setNickname(nick);
});
//nickname정보 받아서 저장
socket.on("updateNicks", (nickInfo) => {
setUserList(nickInfo);
});
}, []);
- 2) [chatList]: notice이벤트 발생 시 사용됨, mount로 설정시 socket으로 이벤트를 받아도, mount될 때의 chatList값을 기준으로 이벤트가 발생하기 때문에 chatList가 변경되면 이후 notice이벤트도 변경된 chatList를 기준으로 발생하도록 설정한 것이다
useEffect(() => {
//알림문구
socket.on("notice", (notice) => {
const newChatList = [
...chatList,
{
type: "notice",
content: notice,
},
];
setChatList(newChatList);
});
scrollDiv.current?.scrollIntoView({ behavior: "auto" }); //smooth
}, [chatList]);
- 3) [nickname, chatList]: 함수작업을 저장하는 useCallback을 사용하여 코드를 작성후 useEffect를 사용하여 함수를 불러오도록함, chatList는 위와 같은 이유로 사용, nickname 변경을 인지하지 않으면 null값(초기값)으로 내 채팅을 인식을 못하기 때문에 nickname state변경도 감지하도록함
const addChatList = useCallback(
(data) => {
// console.log(data); //{id, message, isDm?}
const type = data.id === nickname ? "me" : "other";
const content = `${data.isDm ? "[DM]" : ""} ${data.message}`;
const isDm = data.isDm;
const newChatList = [
...chatList,
{
type: type,
content: content,
isDm: isDm,
name: data.id,
},
];
setChatList(newChatList);
},
[nickname, chatList]
);
useEffect(() => {
socket.on("message", addChatList);
}, [addChatList]);
- dm을 위한 user목록 관리를 위해서 userList가 변경될 때마다 select의 option을 기억하도록함
const userOption = useMemo(() => {
const options = [];
for (let key in userList) {
if (key !== socket.id)
options.push(<option value={key}>{userList[key]}</option>);
}
return options;
}, [userList]);
- 배열의 key를 하나씩 가져올 수 있음, 대괄호 표기법으로 각 value로도 접근가능
- 예시 코드
const options = [];
for (let key in userList) {
if (key !== socket.id)
options.push(<option value={key}>{userList[key]}</option>);
}
- 채팅으로 scroll이 생성될 경우 아래로 계속 내려가도록 설정
- 채팅창 아래 div빈 태그 생성
- ref로 지정
- chatList가 변경되면(useEffect) ref.current요소에 scrollIntoView()사용
- behavior는 애니메이션 설정, auto는 화면이 곧바로 딱딱하게 내려감, smooth는 부드럽게 내려감
scrollDiv.current?.scrollIntoView({ behavior: "auto" });
- Object.values(객체이름)으로 객체의 값들 목록에 접근가능
- .includes(value): 해당 value가 있으면 true -> 중복
- .indexOf(value) > -1: 해당 value가 있으면 index값(0,1,2..) 없으면 -1
- delete 객체[key]로 삭제
delete nickInfo[socket.id];
- chat관련 state를 db에 저장
- 방에 입장하면 불러오기?
- room 이름을 db에 저장?
- 채팅으로 이미지파일도 보내려면?