채팅방 UI
입장 전
입장 후
필요한 패키지들을 설치 후에
[클라이언트]
const socketio = io.connect("http://34.231.209.***/");
[서버]
const http = require("http");
const Server = require("socket.io").Server;
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: "*",
},
});
io.on("connection", (socket) => {
socket.on("disconnect", () => {
console.log("연결 해제!");
});
socket.on("chat", (data) => {
io.emit("chat", data);
});
});
클라이언트에서 소켓을 뚫을 주소와 연동을 합니다. (서버 배포의 IP주소이기 때문에 보안상 끝 3자리는 ***로 표기하였습니다.)
<div>
{chatOn ? (
<div class="container col-9 m-1 p-3 bg-light rounded shadow-lg mx-auto ">
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
{/* 윗라인구성 */}
<div class="container">
<div class="row">
<div class="col-1">
<h3 onClick={() => logout()}>
<i class="bi bi-arrow-left arrow "></i>
</h3>
</div>
<div class="col-6 mx-auto ">
<div class="container bg-secondary rounded-pill">
<h5 class="text-center text-light ">닉네임 : {user}</h5>
</div>
</div>
</div>
</div>
</div>
{/* 채팅내용 */}
<div id="box" class="container rounded chat_container">
<ChatsList />
</div>
{/* 아래라인 */}
<div class="container pt-3">
<InputText addMessage={addMessage} />
</div>
</div>
) : (
<UserLogin setUser={setUser} setChatOn={setChatOn} />
)}
</div>
채팅방의 전체 구조를 담을 Container입니다. 삼항 연산자를 통해 입장하기를 누르지 않았다면 UserLogin 화면이 보이고 입장하기를 눌렀다면 윗라인 (나가기, 닉네임 정보), 채팅내용, 아래라인 (Input) 들이 보입니다. 차근차근 한 개씩 뜯어봅시다.
ChatContainer에 구성된 컴포넌트 : UserLogin, ChatBox (ChatList), InputText
<div class="container col-9 m-1 p-3 bg-light rounded shadow-lg mx-auto chat_container_login ">
<div class=" container">
<div class="container ">
<button
class=" btn btn-lg press_btn mt-2 d-grid gap-2 col-11 mx-auto"
onClick={() => {
handleSetUser();
}}
style={button}
>
입장하기
</button>
</div>
</div>
</div>
"입장하기" 버튼을 통해 채팅방으로 들어가기 위한 단순 "덮개"라고 생각이 되므로 간단한 구성으로 만들었습니다.
위에 전체코드에서 볼 수 있다시피 Container로 부터 props로 2가지를 전달 받습니다.
이렇게 받은 2가지의 Props 조작을 UserLogin의 handleSetUser function에서 다룰 예정인데,
명시된 변수 이름처럼
1. sesstionStorage에 저장되어 있는 Nickname을 꺼내와서 전달 (상단 정보에 띄울 용)
2. 채팅방의 입장상태를 변경
을 하기 위해 사용됩니다.
// 입장하기를 눌렀을 때
function handleSetUser() {
setUser(sessionStorage.getItem("Nickname"));
setChatOn(true);
}
{chatOn ? ( // 등록 해놨던 유저라면 상단바와 대화창 다 불러오기
<div class="container col-9 m-1 p-3 bg-light rounded shadow-lg mx-auto ">
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
{/* 윗라인구성 */}
<div class="container">
<div class="row">
<div class="col-1">
<h3 onClick={() => logout()}>
<i class="bi bi-arrow-left arrow "></i>
</h3>
</div>
<div class="col-6 mx-auto ">
<div class="container bg-secondary rounded-pill">
<h5 class="text-center text-light ">닉네임 : {user}</h5>
</div>
</div>
</div>
</div>
</div>
{/* 채팅내용 */}
<div id="box" class="container rounded chat_container">
<ChatsList />
</div>
{/* 아래라인 */}
<div class="container pt-3">
<InputText addMessage={addMessage} />
</div>
</div>
UserLogin을 통해 chatOn의 상태가 True로 바뀌었을 때, 삼항 연산자를 통해 채팅방 내부를 보여줍니다.
뒤로가기 (logout - 채팅방나가기)를 통해 다시 입장하기로 돌아갈 수 있습니다.
function logout() {
setChatOn(false);
}
UserLogin에서 세션스토리지 (탭기반)를 통해 이미 받아온 닉네임은 setUser에 저장이 되어 있으므로 변경 하지 않고 ChatOn의 상태를 false로 바꾸어 다시 입장하기 버튼이 보이도록 하여 채팅방에서 나간 것이라고 간주하게 합니다.
또한 닉네임은 앞서 언급한대로 세션스토리지, setUser를 통해 값을 변경한 "user" 변수에 저장되어 있습니다.
"성혁"이 리시버일 때 (상대화면)
"성혁"이 센더일 때 (내 화면)
// [리시버 화면]
export default function ChatBoxReciever({ user, message }) {
return (
<div
style={{
display: "flex",
justifyContent: "flex-start",
flexDirection: "row",
}}
>
<p
style={{
padding: 10,
backgroundColor: "#dbdbb2",
borderRadius: 10,
maxWidth: "60%",
}}
>
<strong style={{ fontSize: 13 }}>{user}</strong> <br></br>
{message}
</p>
</div>
);
}
// [센더 화면]
export function ChatBoxSender({ user, message }) {
return (
<div
style={{
display: "flex",
paddingRight: 10,
justifyContent: "flex-end",
flexDirection: "row",
}}
>
<p
style={{
padding: 10,
backgroundColor: "#d4d4d4",
borderRadius: 10,
maxWidth: "60%",
}}
>
<strong style={{ fontSize: 13 }}>{user}</strong> <br></br>
{message}
</p>
</div>
);
}
보시다시피 채팅방이기 때문에 두 가지 화면이 존재합니다.
1. 상대방 화면이자 메세지를 받는 리시버화면
2. 내 화면이자 메세지를 보내는 센더화면
둘 모두 Container에서 user와, message를 props로 전달 받아 활용하게 되고 그 구성은 매우 간단하게 되어있습니다.
단순히 유저의 이름과, 보낸 message의 내용이 담기며 UI의 구성이 "flex-end"이냐 "flex-start"이냐의 차이로 구분이 된다.
Container에서 전달된 props는 다음과 같습니다.
const [chats, setChats] = useState([]);
function ChatsList() {
return chats.map((chat, i) => {
if (chat.user === user) {
return <ChatBoxSender key={i} message={chat.message} user={chat.user} />;
} else {
return <ChatBoxReciever key={i} message={chat.message} user={chat.user} />;
}
});
}
<div id="box" class="container rounded chat_container">
<ChatsList />
</div>
먼저 배열 State로 구성된 chat이라는 state에 추후 InputText를 통해 넣어준 메세지와 user를 전달하는데
내 user name에 해당하는 chat의 message들은 Sender입장으로 전해져나의 채팅방 화면의 오른쪽에 렌더링이 되고, 그렇지 않다면 왼쪽에 렌더링이 됩니다.
마지막으로 메세지를 전송하는 부분인 하단부분입니다.
우선, Container는 addMessage라는 함수를 props로 전달 해줍니다.
<div class="container pt-3">
<InputText addMessage={addMessage} />
</div>
// 내가 한 말들을 서버에 emit요청
function sendChatToSocket(chat) {
socketio.emit("chat", chat);
}
function addMessage(chat) {
const newChat = { ...chat, user };
// 원래 있던 채팅의 내용들과 새로운 채팅 내용들을 합침
setChats([...chats, newChat]);
// 합친 내용들을 서버에 전달
sendChatToSocket([...chats, newChat]);
}
그 구성으로는 다음과 같습니다. 새로운 내용의 채팅들을 배열 state인 Chat에 합쳐주고 그 내용들을 서버에 구헌한 socket에 보내어 그를 통해 전체 채팅방에 뿌려줍니다.
더욱 상세한 내용을 위해 inputText.js를 보자면
// inputText.js
<div class="container">
<div class="row">
<div class="col-sm-9">
<input style={styles.textarea} rows={6} placeholder="할 말을 입력하세요..." value={message} onChange={(e) => setMessage(e.target.value)} onKeyPress={handleOnKeyPress}></input>
</div>
<div class="col-sm-3">
<button
class="btn btn-lg col-11"
onClick={() => {
addAMessage();
}}
style={styles.button}
>
전송
</button>
</div>
</div>
</div>
그 후 input과 button을 생성한 후에 input의 value로 message (state 변수)를, button의 onClick으로 inputText에서 구현한 addAmessage()를 넣습니다 (메세지 한 개 추가용)
function addAMessage() {
addMessage({
message,
});
setMessage("");}
props를 통해 받아온 addMessage 함수를 활용할 것인데 addAmessage에 방금 보낸 메세지 한 개 전달하고 다시 상태를 비워 다음 메세지를 보낼 수 있도록 합니다.
inputText의 addAMessage를 통해 메세지 한 개를 보내면
props로 받아온 함수 addMessage를 통해 내가 친 말이 기존에 있던 대화와 합쳐지고 그것들을 socketio.emit을 통해 보냅니다. 그렇게하여 socket.on으로 대기중인 "chat"에 전달이 됩니다.
그렇게 서버에서 "chat"을 받았다면 io.emit을 통해 소켓에 들어와있는 모든 사용자들에게 뿌리고 클라이언트의 socketio.on을 통해 받아 센더와 리시버를 구별하여 뿌려는 과정입니다.