#0 개요
구현 과정은 다음과 같다.
1. react-hook-form을 적용하여, 채팅을 입력하지 않고 제출하는 경우에 대한 block 만들기.
2. 정상적으로 제출시 message state에 저장되고 채팅이 출력되는 로컬 로직부터 구현
<< 구현 방식을 변경했다.
채팅 UI 변경은 socket.on으로 서버로부터 데이터를 받을 때만. 이유는 아래와 같다
3. socket emit 이벤트로 작성한 채팅을 보내는 함수만 작성
4. Shadcn 의 Sooner를 사용해서 빈 채팅창 입력에 대한 오류 출력 UI 작성하기
Sonner
An opinionated toast component for React.
sonner.emilkowal.ski
5. 메세지 전송시 해당 시간을 저장해두고, 다음 메세지를 전송하는 순간 시간을 비교해서 2초 이내이면 막는 로직 만들기
6. 이것도 Sooner로 출력
#1 react-hook-form으로 입력 컴포넌트 작성 및 Sonner 적용
import { faPaperPlane } from "@fortawesome/free-solid-svg-icons";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useForm, FieldErrors } from "react-hook-form";
import { toast } from "sonner";
import { Socket } from "socket.io-client";
interface IFormProps {
playerNum: number;
socket: Socket; // 부모에서 전달받을 소켓 인스턴스
}
interface IForm {
chat: string;
}
function ChatForm({ playerNum, socket }: IFormProps) {
const paperPlane: IconDefinition = faPaperPlane;
const {
register,
handleSubmit,
resetField,
formState: { errors },
} = useForm<IForm>();
const onValidSubmit = async (data: IForm, e?: React.BaseSyntheticEvent) => {
//local storage에서 토큰 가져오기, player num 가져오기기
//axios api 작성성
e?.preventDefault();
try {
// 1) 서버로 메시지 전송
// 서버에서도 "chat" 이벤트를 수신하여 모든 클라이언트에게 broadcast하도록 구현
const newMessage = {
id: Date.now(), // 간단히 임시 ID
player: playerNum,
text: data.chat,
};
socket.emit("chat", newMessage);
// 메시지 전송 로직 성공 시 input 비우기
resetField("chat");
} catch (error) {
// 에러 처리
console.error(error);
}
};
const onInvalidSubmit = (errors: FieldErrors<IForm>) => {
if (errors.chat?.type === "required") {
// "빈 채팅을 입력할 수 없습니다" 에러 발생 시, 원하는 custom function 실행
toast.error(errors.chat.message);
}
};
return (
<form
className="w-full h-full bg-[#07070a4d] focus-within:bg-neutral-900 rounded-lg shadow-lg border-2 border-solid border-[#555555] px-4 flex items-center text-sm"
onSubmit={handleSubmit(onValidSubmit, onInvalidSubmit)}
action=""
>
<input
{...register("chat", {
required: "빈 채팅을 입력할 수 없습니다.",
maxLength: {
value: 200,
message: "최대 200자리까지 입력할 수 있습니다다",
},
})}
className="bg-transparent text-[#848484] focus:text-[#dddddd] w-full"
placeholder="채팅 입력하기"
type="text"
autoComplete="off"
/>
<div className="bg-[#848484] w-[1px] h-6"></div>
<button className="w-6 h-6 ml-2">
<FontAwesomeIcon icon={paperPlane} className="text-[#848484]" />
</button>
</form>
);
}
export default ChatForm;
onInvalid 함수로 toast를 발생시켜 UI를 띄운다.
#2 Socket을 연결하기
const socket: Socket = io("http://localhost:3000");
//컴포넌트 내부
useEffect(() => {
socket.on("chat", (data: { id: number; player: number; text: string }) => {
setMessages((prev) => [
...prev,
{
id: data.id,
player: data.player,
text: data.text,
// 보낸 playerNum과 현재 사용자의 playerNum이 같으면 isMine = true
isMine: data.player === currentPlayerNum,
},
]);
});
return () => {
socket.off("chat");
};
}, [currentPlayerNum]);
다음과 같은 형태로 socket에서 받는 형태로 진행한다.
#3 개발 완료된 UI
1. 채팅방 입장
채팅방 입장시 chat:room:emit 이벤트를 클라이언트 쪽에서 발생하게 했다.
그럼 백엔드에서 해당 유저를 소켓 룸에 넣어주고, chat:room:send 이벤트를 해당 룸에 있는 사람들에게 모두 보내준다.
chat:room:send 에는 해당 게임 방에 대한 방 번호, 제목, 유저들 등의 정보가 전달되며,
그 정보를 바탕으로 UI를 업데이트하여 실시간으로 유저들이 방에 들어온 정보를 업데이트한다.
2. 채팅 입력 로직
채팅 입력시 chat:emit 이벤트가 발생해서 사용자가 입력한 chat string을 소켓 통신으로 보낸다.
그럼 서버에서 채팅방 유저들에게 chat:send 이벤트를 통해 채팅 정보들을 보내준다.
'프로젝트 기록 > LLM 게임 프로젝트' 카테고리의 다른 글
개발 리뷰 -14- 게임 시스템 개발 + 최종 데모 영상 (0) | 2025.02.26 |
---|---|
개발 리뷰 -13- socket provider와 hook만들기 (0) | 2025.02.06 |
개발 리뷰 -11- 게임 채팅 페이지 개발 (0) | 2025.01.23 |
개발 리뷰 -10- 메인 페이지 개발 (0) | 2025.01.23 |
개발 리뷰 -9- 실시간 채팅 구현을 위한 Websocket, webRTC 선행 (0) | 2025.01.16 |