๐Ÿ“Œ (2) ์ž์ฒด ํ”„๋กœ์ ํŠธ with-dog ์ฑ„ํŒ…๊ตฌํ˜„

seul_velogยท2022๋…„ 10์›” 6์ผ
1

๊ตฌํ˜„ ์˜์ƒ

๋ฐ˜๋ ค๊ฒฌ MBTI ๊ฒ€์‚ฌ๋ฅผ ํ†ตํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์›น ์„œ๋น„์Šค
๐Ÿ“Œ ์‚ฌ์ดํŠธ : https://withdog.me/
๐Ÿ“Œ ์œ ํŠœ๋ธŒ
1๋ถ€ - ๋ฉ”์ธ & ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ & MBTI๊ฒ€์‚ฌ
2๋ถ€ - ๐Ÿ‘‰ ์ฑ„ํŒ… โœ…
3๋ถ€ - ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ


๋ธ”๋กœ๊ทธ ๊ธฐ๋ก

โœจ ํ”„๋กœ์ ํŠธ ๋“ค์–ด๊ฐ€๊ธฐ ๐Ÿ‘‰ (1) ์ž์ฒด ํ”„๋กœ์ ํŠธ with-dog UI
โœจ ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๐Ÿ‘‰ (2) ์ž์ฒด ํ”„๋กœ์ ํŠธ with-dog ์ฑ„ํŒ…๊ตฌํ˜„

ํ”„๋กœ์ ํŠธ ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ตฌํ˜„์— ์•ž์„œ Websocket๊ณผ Socket.IO ๊ฐœ๋… ์•Œ๊ณ  ์ตํžˆ๊ธฐ. ์‹ค์Šต ํ”„๋กœ์ ํŠธ !

1. JavaScript + Websocket ๐Ÿ‘‰ WebSocket ์œผ๋กœ ์ฑ„ํŒ…๊ธฐ๋Šฅ ๊ตฌํ˜„ ํ•ด๋ณด๊ธฐ
2. JavaScript + Socket.IO & React + Socket.IO ๐Ÿ‘‰ Socket.IO๋กœ ์ฑ„ํŒ…๊ธฐ๋Šฅ ๊ตฌํ˜„ ํ•ด๋ณด๊ธฐ




Socket.IO๋กœ ChatRoom ๋งŒ๋“ค๊ธฐ

โœ๏ธ ๋จผ์ € ํ˜ผ์ž node.js & react & Socket.IO ๋ฅผ ์ด์šฉํ•˜์—ฌ server์™€ client๊ฐ„์˜ ๊ธฐ๋ณธ์ ์ธ ํ†ต์‹  ๊ตฌํ˜„ ๋ฐ ํ™•์ธ ํ›„ ๋ฐฑ์—”๋“œ์™€ ํ˜‘์—…์„ ์ง„ํ–‰ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.๐Ÿ˜€


โœจ nodemon, express, socket.io-client ์„ค์น˜ ๋ฐ ์„ธํŒ…

// package.json

"scripts": {
    "dev": "nodemon",
		...
}



์„œ๋ฒ„์—ฐ๊ฒฐ ๋ชจํ—˜

1) node.js ์—์„œ require ์“ฐ๊ธฐ

const express = require('express');
const SocketIO = require('socket.io');
const http = require('http');

2) ์ฝ”๋“œ๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ ์งœ๊ณ  ์‹คํ–‰์€ nodemon public/server.js โ† ์ด๋•Œ ๊ฒฝ๋กœ์— public์„ ์ƒ๋žตํ•˜๋ฉด ์•ˆ๋œ๋‹ค! (nodemon์ด ์•„๋‹Œ node๋งŒ ์“ธ ๊ฒฝ์šฐ ํ•ซ๋ฆฌ๋กœ๋“œ๊ฐ€ ์•ˆ๋œ๋‹ค!)

"scripts": {
    "dev": "nodemon", 
    "start": "react-scripts start",
		...
}

3) connection ์˜คํƒ€ ํ™•์ธํ•˜๊ธฐ



์„œ๋ฒ„์™€ ํด๋ผ Socket.IO๋กœ ์ž…์žฅ ๋ฉ”์„ธ์ง€ ์ฃผ๊ณ ๋ฐ›๊ธฐ

โœจ 1) ์ž…์žฅ ์†Œ์ผ“

  • ์ž…์žฅ์‹œ ๋‹‰๋„ค์ž„๊ณผ ๋ฐฉ์ด๋ฆ„์„ ์„œ๋ฒ„๋กœ ๋„˜๊ธด๋‹ค.
const [message, setMessage] = useState('');
const [messages, setMessages]: any = useState([]);

useEffect(() => {
    socket.emit('์ž…์žฅ', { nickname, room });
    console.log('1) ์ž…์žฅ ์†Œ์ผ“ / ํด๋ผ->์„œ๋ฒ„');
  }, []);

โœจ 2) ์„œ๋ฒ„์—์„œ ๋ฐ›๊ณ  ๋ณด๋‚ด๊ธฐ

socket.on('์ž…์žฅ', ({ nickname, room }) => {
    socket.join(room);

// ๋ฉ”์„ธ์ง€๊ฐ€ ๋ฐ”๋กœ ์ฝ˜์†”์— ์ฐํžŒ๋‹ค.
    socket.emit('add_message', {
      user: 'admin',
      text: `${nickname}, ์–ด์„œ์˜ค์‹œ๊ฐœ! `,
    });

// ๋‚˜๋ฅผ ์ œ์™ธํ•œ ์‚ฌ๋žŒ์—๊ฒŒ ์ฐํžˆ๋ฏ€๋กœ ๋ฐ”๋กœ ์ฐํžˆ์ง€ ์•Š๋Š”๋‹ค. -> ๋’ค๋กœ๊ฐ€๊ธฐ ํ›„ ๋‹ค์‹œ ์ฑ„ํŒ…๋ฐฉ์— ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ ๋ณด์ด๊ฒŒ ๋œ๋‹ค.
// ์ด๊ฒŒ ์œ„์˜ ๋กœ์ง์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ  ์ฝ˜์†”์— ์ฐํžˆ์งˆ ์•Š์•„์„œ ์‚ฝ์งˆํ–ˆ์Œ..
    socket.broadcast.to(room).emit('add_message', {
      user: 'admin',
      text: `${nickname} ๋‹˜์ด ๋“ค์–ด์˜ค์…จ์Šต๋‹ˆ๋‹ค!`,
    });
    console.log('2) ๋ฉ”์„ธ์ง€ ์ถ”๊ฐ€ / ์„œ๋ฒ„->ํด๋ผ');
  });

โœจ 3) ๋ฉ”์„ธ์ง€ ์ถ”๊ฐ€ํ•˜๊ธฐ

socket.on('add_message', message => {
    setMessages([...messages, message]);
    console.log('3) ๋ฉ”์„ธ์ง€ ์ถ”๊ฐ€ / ํด๋ผ');
  });
console.log('4) ๋ฉ”์„ธ์ง€ ๋ฐฐ์—ด ํ™•์ธ', messages);

โœ๏ธ console ํ™•์ธํ•ด๋ณด๊ธฐ



ChatRoom UI

โœจ ์ฑ„ํŒ…๋ฐฉ ํŽ˜์ด์ง€ UI ๊ตฌํ˜„

แ„†แ…ฎแ„Œแ…ฆ-1

  1. ์ฑ„ํŒ…๋ฐฉ UI ๋ ˆ์ด์•„์›ƒ ์ž‘์„ฑ
  • ChatRoom ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ
  • ํ˜„์žฌ ์‚ฌ์šฉ์ž ์ƒํƒœ๊ฐ’ isSentByCurrentUser ์— ๋”ฐ๋ฅธ ๋™์  UI ์„ค์ •
  1. style type ์„ค์ •
  • styled.div<{ currentUser: boolean }>

โœจ ๋ฉ”์„ธ์ง€๋ฅผ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌํ•ด์ฃผ๊ธฐ

// index.tsx
return (
    <ChatRoomContainer>
      <TitleBar />
      <Messages messages={messages} />  // ๋ฉ”์„ธ์ง€ ๋‚ด๋ ค ๋ณด๋‚ด๊ธฐ
      <Input
        message={message}
        setMessage={setMessage}
        sendMessage={sendMessage}
      />
    </ChatRoomContainer>
  );



Chatting ๊ธฐ๋Šฅ ๊ตฌํ˜„

Sep-22-2022 13-28-51

1. ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ตฌํ˜„

  • ChatRoom Router ์„ค์ •
  • node.js, socket.io๋ฅผ ํ™œ์šฉ, server์™€ client๊ฐ„์˜ ์†Œ์ผ“ ํ†ต์‹  ๊ตฌํ˜„ (ํ”„๋ก ํŠธ๋‹จ ํ…Œ์ŠคํŠธ)
    • node.js -> server ์ž‘์„ฑ
    • react -> client ์ž‘์„ฑ
  • ๋ฐฑ์—”๋“œ(python socketioํ™œ์šฉ) ์™€ ํ˜‘์—…
  • ์ฑ„ํŒ…๋ฐฉ ๋ถ„๋ฆฌ

2. ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ

  • ๋ฆฌ๋•์Šค persist ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ์šฉ -> ์ƒˆ๋กœ๊ณ ์นจ์‹œ์—๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ์Œ
  • user ๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ค๊ธฐ ๋ฐ useSlector type ์ •์˜
  • nickname ๋ฐ room id, room name ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

3. ์ฑ„ํŒ…๋ฃธ ๋ Œ๋”๋ง

  • ๋กœ๊ทธ์ธ ๋œ ์‚ฌ์šฉ์ž๊ฐ€ mbti ๊ฒ€์‚ฌ๋ฅผ ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด none์œผ๋กœ ์ฒ˜๋ฆฌ & ๋กœ๊ทธ์ธ์„ ํ–ˆ๋‹ค๋ฉด ํ•ด๋‹นํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ socket ํ†ต์‹  ํ›„ ์ €์žฅ๋œ mbti ๊ฐ’์„ ๊ฐ€์ ธ์™€์„œ ๋ Œ๋”๋ง
  • ์œ ์ €๊ฐ€ ๋“ค์–ด์™”์„ ๋•Œ, ๋‚˜๊ฐ”์„ ๋•Œ ๊ด€๋ฆฌ์ž ์•Œ๋ฆผ ๋ฉ”์„ธ์ง€ ์ ์šฉ
  • ํ˜„์žฌ ์‹œ๊ฐ„์„ ๋‚˜ํƒ€๋‚ด๋Š” ํ•จ์ˆ˜ ์ƒ์„ฑ -> currentTime ์ƒํƒœ์ €์žฅ ํ›„ socket ํ†ต์‹ ์„ ํ•˜์—ฌ UI๋ Œ๋”๋ง
  • ์ฑ„ํŒ… ๋ฉ”์„ธ์ง€๊ฐ€ ์ฑ„ํŒ…์ฐฝ ์ด์ƒ์œผ๋กœ ๋Š˜์–ด๋‚  ๊ฒฝ์šฐ ์ž๋™ ํ•˜๋‹จ ์Šคํฌ๋กค ์ ์šฉ

โœจ ๋ฐฐ์—ด์„ ๋ฐ›์•„์„œ map ์„ ๋Œ๋ฆด ๋•Œ ์ฃผ์˜ํ•˜๊ธฐ

const Messages = messages => {
  console.log('messages', messages);
  return (
    <MessagesContainer>
      {messages.map((message, i) => (
        <div key={i}>
          <Message message={message}></Message>
        </div>
      ))}
    </MessagesContainer>
  );
};

์œ„์™€ ๊ฐ™์ด ์ž‘์„ฑํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค

โ“ย ์ด์œ ๊ฐ€ ๋ฌด์—‡์ผ๊นŒ? ์ด์ „๊นŒ์ง€์˜ ๊ฒฝํ—˜์„ ๋ฐ”ํƒ•์œผ๋กœ ์ถ”์ธกํ•ด๋ณด๋ฉด map์„ ๋Œ๋ฆฌ๊ธฐ์œ„ํ•œ messages ํ˜•์‹์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๋“ฏ ํ•˜๋‹ค. ๐Ÿง


โ—๏ธ ํ•ด๊ฒฐ์€ props์— ์žˆ์—ˆ๋‹ค.
const Messages = ({ messages }) => { ... โ†’ ์ด๋ ‡๊ฒŒ () ์•ˆ์— {} ๋กœ ์ž‘์„ฑํ•ด ์ฃผ๋ฉด์„œ ํ•ด๊ฒฐ!

๐Ÿ“Œย  ๊ฐ์ฒด์—๋Š” .map ๋ฉ”์„œ๋“œ๊ฐ€ ์—†๋‹ค. ์˜ค๋กœ์ง€ ๋ฐฐ์—ด์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์ž„์„ ์œ ์˜!
๋‚˜๋Š” messages๋ฅผ props๋กœ ๋„˜๊ฒจ์ค„ ๋•Œ ์•„๋งˆ ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๋„˜๊ฒจ์คฌ์„ ๊ฒƒ์ด๋‹ค! ๋”ฐ๋ผ์„œ ๋ฐ›์•„์˜ฌ ๋•Œ ๊ฐ์ฒด๋ฅผ ํ•œ๋ฒˆ ํ’€์–ด์ค˜์•ผ ํ•œ๋‹ค! ๐Ÿ˜€


โœจ ๋ฉ”์„ธ์ง€๋ฅผ ํ™”๋ฉด์— ์ถœ๋ ฅํ•˜๊ธฐ

โœ๏ธ 1์ฐจ์‹œ๋„

useEffect(() => {
    socket.on('add_message', ๋ฐ›์€๋ฉ”์„ธ์ง€ => {
      setMessages([...messages, ๋ฐ›์€๋ฉ”์„ธ์ง€]);
      console.log('๊ธฐ์กด ๋ฉ”์„ธ์ง€๋“ค:', messages);
      console.log('์ถ”๊ฐ€๋  ๋ฉ”์„ธ์ง€๋ฐ์ดํ„ฐ:', message);
    });
  }, []);
  • ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•  ๊ฒฝ์šฐ messages์˜ ๋ฐฐ์—ด์ด ์ถ”๊ฐ€๋˜์ง€ ์•Š๋Š”๋‹ค. (์•„๋ฌด๋ฆฌ ์—ฌ๋Ÿฌ๋ฒˆ ์ž…๋ ฅํ•ด๋„ ๋ฐฐ์—ด์ด ์ถ”๊ฐ€๋˜์ง€ ์•Š์Œ)


โœ๏ธ 2์ฐจ์‹œ๋„
๊ฒฝํ—˜์ƒ ์–•์€๋ณต์‚ฌ์™€ ๊นŠ์€๋ณต์‚ฌ, ์–•์€ ๋น„๊ต์™€ ๊นŠ์€ ๋น„๊ต์™€ ๊ด€๋ จ์ด ์žˆ์„ ๊ฒƒ ๊ฐ™์•˜๋‹ค. ๐Ÿค”

์—ฌ๋Ÿฌ ์‹œ๋„, ๊ตฌ๊ธ€๋ง ๋์— ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝ! โ†’ messages ๋ฐฐ์—ด์— ๊ธฐ์กด๋ฉ”์„ธ์ง€์— ์ด์–ด์„œ ์ž˜ ์ถ”๊ฐ€๊ฐ€ ๋˜์—ˆ๊ณ  ํ™”๋ฉด์—๋„ ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ฉ”์„ธ์ง€๊ฐ€ ์ž˜ ๊ทธ๋ ค์กŒ๋‹ค!โœจ

useEffect(() => {
    socket.on('add_message', message => {
      const newMsg = message;
      setMessages(messages => [...messages, newMsg]);
    });
  }, []);


โœจ ํ•œ๊ธ€ ์ž…๋ ฅ ์ค‘๋ณต ์ด์Šˆ ํ•ด๊ฒฐํ•˜๊ธฐ

โ“ ๋ฌธ์ œ ํŒŒ์•…ํ•˜๊ธฐ

  • ๋ฉ”์„ธ์ง€๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค ๋‘๋ฒˆ์”ฉ ์ถœ๋ ฅ๋˜๋Š” ๊ฒฝ์šฐ
    โ†’ ํ•œ๊ธ€์ž…๋ ฅ + Enter

  • ๋ฉ”์„ธ์ง€๋ฅผ ์ž…๋ ฅํ•  ๋•Œ ํ•œ ๋ฒˆ์”ฉ ์ž˜ ์ถœ๋ ฅ๋˜๋Š” ๊ฒฝ์šฐ
    โ†’ ์˜๋ฌธ์ž…๋ ฅ / ํŠน์ˆ˜๋ฌธ์ž ์ž…๋ ฅ / ์ˆซ์ž์ž…๋ ฅ / ํ•œ๊ธ€ ์ž…๋ ฅ + ์ง์ ‘ ์ „์†ก๋ฒ„ํŠผ ๋ˆ„๋ฅด๊ธฐ


โ—๏ธ ํ•ด๊ฒฐํ•˜๊ธฐ

  • ์—ฌ๊ธฐ์„œ ํ•œ๊ธ€์ž…๋ ฅ์„ ํ• ๋•Œ๋งŒ ๋‘ ๋ฒˆ์”ฉ ์ถœ๋ ฅ๋œ๋‹ค๋Š” ๊ฒƒ, ๊ทธ๋ฆฌ๊ณ  ํ•œ๊ธ€ ์ž…๋ ฅ์„ ํ•˜๋”๋ผ๋„ ์ „์†ก ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ํ•œ ๋ฒˆ ์ถœ๋ ฅ์ด ๋œ๋‹ค๋Š” ์ ์„ ์บ์น˜ํ•ด์„œ Input ์ปดํฌ๋„ŒํŠธ์— ์ž‘์„ฑํ•œ ์ด๋ฒคํŠธ์™€ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์„ ์—ญ์ถ”์ !!

  • ๊ตฌ๊ธ€๋ง์„ ํ†ตํ•ด ์•Œ์•„๋‚ธ ๊ฒƒ์€ onKeyDown ๋Œ€์‹  onKeyPress ๋ฅผ ์ž‘์„ฑ!
    (ํ•˜์ง€๋งŒ ๋‚ด๊ฐ€์•Œ๊ธฐ๋กœ onKeyPress๋Š” ๋”์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ๋ง๋ผ๊ณ  ํ•˜๋˜๋ฐ.. ๐Ÿงย ) ๐Ÿ‘‰ MDN
    ๐Ÿ‘‰ ๊ด€๋ จ(์ฐธ๊ณ ํ•œ) ๊ธ€ ๋ณด๊ธฐ

๐Ÿ“Œ onKeyDown ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ
โ“ onKeyDown์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํ•ด๊ฒฐํ•  ๋ฐฉ๋ฒ•์€ ์—†์„๊นŒ?

onKeyDown={e =>
            e.key === 'Enter' && e.nativeEvent.isComposing === false
              ? sendMessage(e)
              : null
          }

โ—๏ธ ์ด๋ ‡๊ฒŒ e.nativeEvent.isComposing === false ์กฐ๊ฑด์„ ๋„ฃ์–ด์คŒ์œผ๋กœ์จ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.๐Ÿ˜€โœจ ๐Ÿ‘‰ ์ฐธ๊ณ ํ•œ ๊ธ€



โœจย ์ฑ„ํŒ…๋ฐฉ ์ž๋™ ์Šคํฌ๋กค ์‚ฌ์šฉ์„ฑ ๊ฐœ์„ 

  • ๋งŒ์•ฝ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด react-scroll-bottom ์„ ํ™œ์šฉํ•  ์žˆ๋‹ค.

๐Ÿ“Œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ง์ ‘ ์งœ๋ณด๊ธฐ!

โœ๏ธ 1์ฐจ ์‹œ๋„

const scrollToBottom = () => {
    if (messagesEndRef.current) {
      messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight;
    }
  };
  • ์Šคํฌ๋กค์ด ์ด๋™๋˜์ง€๋งŒ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ด๋™๋˜์ง€ ์•Š์•„์„œ ๊ตฌ๊ธ€๋ง์„ ํ•ด๋ณด์•˜๋‹ค.

โœ๏ธ 2์ฐจ ์‹œ๋„

const scrollToBottom = () => {
    if (messagesEndRef.current) {
      messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
    }
  };
  • ๊ตฌ๊ธ€๋ง์ค‘ ๊ฐ€์žฅ ๋งŽ์ด ์ถ”์ฒœ๋œ ์ฝ”๋“œ์˜€์ง€๋งŒ ๋‚˜์—๊ฒ ์ž‘๋™๋˜์ง€ ์•Š์•˜๋‹ค.

โœ๏ธ 3์ฐจ ์‹œ๋„

const scrollToBottom = () => {
    if (messagesEndRef.current) {
      messagesEndRef.current.scrollTo({
        top: messagesEndRef.current.scrollHeight,
        behavior: 'smooth',
      });
    }
  };
  • ์ด๋ ‡๊ฒŒ ์ˆ˜์ •ํ•จ์œผ๋กœ์จ ์•„๋ž˜๋กœ ์Šคํฌ๋กค ๋˜๋ฉด์„œ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ด๋™๊นŒ์ง€ ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค!! ๐Ÿ˜Ž

โœจ ๋ฆฌํŒฉํ† ๋ง ๋ฐ ํƒ€์ž… ์ด์Šˆ ํ•ด๊ฒฐ

const scrollToBottom = () => {
    messagesEndRef.current?.scrollTo({ // ?๋ฅผ ์ง€์šฐ๋ฉด ์—๋Ÿฌ
      top: messagesEndRef.current.scrollHeight,
      behavior: 'smooth',
    });
  };
const refTest = useRef<any>(null); // x
const refTest = useRef<HTMLUListElement>(null); // x
const refTest = useRef<HTMLUListElement>(); // x
const messagesEndRef = useRef<HTMLDivElement>(null); // o



โœจย ์‹œ๊ฐ„ ์ถ”๊ฐ€ํ•˜๊ธฐ

์‹œ๊ฐ„ ์ถœ๋ ฅํ•˜๋Š” ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ

const getDate = () => {
    const date = new Date();
    const hours = date.getHours();
    const minutes = date.getMinutes();
    const currentTime = hours > 12 ? `์˜คํ›„ ${hours - 12}` : `์˜ค์ „ ${hours}`;
    return `${currentTime}:${minutes}`;
  };

โ“์‹œ๊ฐ„์„ ์–ป์–ด๋‚ผ ์ˆ˜ ์žˆ์ง€๋งŒ ๋ชจ๋“  UI๊ฐ€ ํ˜„์žฌ ์‹œ๊ฐ„์„ ๋ฐ˜์˜ํ•œ๋‹ค. โ†’ ์‹œ๊ฐ„ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ด์„œ ์›น์†Œ์ผ“ ํ†ต์‹ ์„ ํ•ด์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค.๐Ÿค”


์‹œ๊ฐ„๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”์„ธ์ง€์— ์ถ”๊ฐ€ํ•˜๊ธฐ

// ํด๋ผ์ด์–ธํŠธ
const [currentTime, SetCurrentTime] = useState('');

const sendMessage = e => {
    e.preventDefault();
    if (message) {
      socket.emit('send_message', message, nickname, room, currentTime, () =>
        setMessage('')
      );
    }
  };

// ์„œ๋ฒ„
socket.on(
    'send_message',
    (message, nickname, room, currentTime, callback) => {
      io.to(room).emit('add_message', {
        user: nickname,
        text: message,
        time: currentTime,
      });
      callback();
    }
  );

// ํด๋ผ์ด์–ธํŠธ
useEffect(() => {
    socket.on('add_message', message => { // ์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฉ”์„ธ์ง€ ๊ฐ์ฒด ์•ˆ์—๋Š” time๋„ ์กด์žฌํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
      const newMwssage = message;
      setMessages(messages => [...messages, newMwssage]);
    });
  }, []);


๋ถ„์— 0๋‹จ์œ„ ์ถ”๊ฐ€ํ•˜๊ธฐ

โœ๏ธ ์ˆ˜์ •์ „ ์ฝ”๋“œ

useEffect(() => {
    const date = new Date();
    const hours = date.getHours();
    const minutes = date.getMinutes();
    const currentTime = hours > 12 ? `์˜คํ›„ ${hours - 12}` : `์˜ค์ „ ${hours}`;

    SetCurrentTime(`${currentTime}:${minutes}`);
  }, [messages]);

โœ๏ธ ์ˆ˜์ •ํ›„ ์ฝ”๋“œ

useEffect(() => {
    const date = new Date();
    const hours = date.getHours();
    const minutes = ('0' + date.getMinutes()).slice(-2);
    const currentTime = hours > 12 ? `์˜คํ›„ ${hours - 12}` : `์˜ค์ „ ${hours}`;

    SetCurrentTime(`${currentTime}:${minutes}`);
  }, [messages]);

๐Ÿ‘‰ ์ฐธ๊ณ ๊ธ€



โœจ ๊ด€๋ฆฌ์ž ์ „์šฉ UI ๋งŒ๋“ค๊ธฐ

const Message = ({ message: { user, text, time }, nickname }) => {
  let isSentByCurrentUser = false;
  if (user === nickname) {
    isSentByCurrentUser = true;
  }

  const storeData = useSelector((state: any) => state);
  const userImage = storeData.user.userData.thumbnail_url;

  if (user === 'ํ•จ๊ป˜ํ•˜๊ฐœ ๊ด€๋ฆฌ์ž') { // 1)
    return (
      <AdminTextBox>
        <Text>{text}</Text>
      </AdminTextBox>
    );
  }

return isSentByCurrentUser ? ( // 2)
    <MessageContainer currentUser={isSentByCurrentUser}>
      <MessageBox>
        <TextContainer>
          <TextData>
            <Time>{time}</Time>
          </TextData>
          <TextBox currentUser={isSentByCurrentUser}>
            <Text>{text}</Text>
          </TextBox>
        </TextContainer>
      </MessageBox>
      <ProfileImg src={userImage} />
    </MessageContainer>
  ) : (
    <MessageContainer currentUser={isSentByCurrentUser}>
      <ProfileImg src={userImage} />
      <MessageBox>
        <UserBox>
          <Nickname>{user}</Nickname>
          <Mbti>INFP</Mbti>
        </UserBox>
        <TextContainer>
          <TextBox currentUser={isSentByCurrentUser}>
            <Text>{text}</Text>
          </TextBox>
          <TextData>
            <ReportIcon src={Siren} />
            <Time>{time}</Time>
          </TextData>
        </TextContainer>
      </MessageBox>
    </MessageContainer>
  );
};

...

const MessageContainer = styled.div<{ currentUser: boolean }>`
  ${props => props.theme.flex.flexBox()}
  justify-content: ${({ currentUser }) => (currentUser ? 'end' : 'start')};
  padding: 1.3rem 1rem 0 1rem;
`;

1) ํ˜„์žฌ user๊ฐ€ ํ•จ๊ป˜ํ•˜๊ฐœ ๊ด€๋ฆฌ์ž ์ด๋ฉด ์•„๋ž˜ ๋ฆฌํ„ด๋ฌธ์„ ์‹คํ–‰
2) ํ˜„์žฌ user๊ฐ€ โ€˜๋‚˜โ€™์ธ์ง€ โ€˜๋‹ค๋ฅธ์œ ์ €โ€™ ์ธ์ง€์— ๋”ฐ๋ผ์„œ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์„ ํ•ด์ค€๋‹ค. styled-components ์—์„œ๋„ ์œ ์ €์— ๋”ฐ๋ผ์„œ ๋‹ค๋ฅธ UI ๋ ˆ์ด์•„์›ƒ์„ ๋ณด์—ฌ์ค€๋‹ค.

โ“ ์œ ์ €๊ฐ€ ๋™์ผํ•œ ๋‹‰๋„ค์ž„์ผ ๊ฒฝ์šฐ
โ†’ ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์ค‘, ๊ฐ™์€ ๋‹‰๋„ค์ž„์„ ๊ฐ€์ง„ ์œ ์ €๊ฐ€ ๊ฐ™์€ ๋ฐฉ์— ์žˆ๋‹ค๋ฉด UI๊ฐ€ ์ค‘๋ณต๋˜๋Š” ์ด์Šˆ ๋ฐœ์ƒ!

โ—๏ธ ์œ ์ €์˜ ๋‹‰๋„ค์ž„์ด ์•„๋‹Œ ๊ณ ์œ  id๋กœ ๊ตฌ๋ถ„ํ•ด์ฃผ๊ธฐ

  let isSentByCurrentUser = false;
  if (user_id === id) {
    isSentByCurrentUser = true;
  }



โœจย ๋– ๋‚ฌ์„ ๋•Œ ๋ฉ”์„ธ์ง€ ์ „์†กํ•˜๊ธฐ

/* ์œ ์ €๊ฐ€ ๋– ๋‚ฌ์„ ๋•Œ */
  socket.on('disconnect', ({ nickname, room }) => {
    io.to(room).emit('add_message', {
      user: 'ํ•จ๊ป˜ํ•˜๊ฐœ ๊ด€๋ฆฌ์ž',
      text: `${nickname} ๋‹˜์ด ๋– ๋‚˜์…จ์–ด์š”.`,
    });
    console.log('์œ ์ €๊ฐ€ ๋– ๋‚ฌ์Šต๋‹ˆ๋‹ค.');
  });
  • ์ด๋•Œ ์„œ๋ฒ„์— console.log ๋Š” ์ž˜ ์ฐํžˆ์ง€๋งŒ add_message ๋กœ ์ „์†ก์ด ๋˜์ง€๋Š” ์•Š๋Š”๋‹ค.
    โ†’ disconnect ๋Š” nickname๊ณผ room์„ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค! โ†’ ์†Œํ†ต ํ›„ ๋ฐฑ์—”๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ๐Ÿ˜€



โœจย ์‹ ๊ณ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

Sep-27-2022 14-25-58

  1. ์ฑ„ํŒ… ์‹ ๊ณ  ๊ธฐ๋Šฅ ๊ตฌํ˜„
  • ChatReportModal ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€
  • ์‹ ๊ณ  ๋ชจ๋‹ฌ ์ถ”๊ฐ€
    - ์‹ ๊ณ  ์‚ฌ์œ  ์ „๋‹ฌ
    - ์‹ ๊ณ  ๋ฉ”์„ธ์ง€ ๊ธ€์ž์ˆ˜ ์ œํ•œ
    - UI์Šคํฌ๋กค ์ œ์–ด ํ•จ์ˆ˜ ์ถ”๊ฐ€
  • ์‹ ๊ณ  ๋ฐ์ดํ„ฐ ๋ฆฌ๋•์Šค ํ™œ์šฉ
...

<ReportIcon
   src={Siren}
   onClick={() => {
      setIsShowModal(true);
      ReportChatData(user_id, message_id, text);
   }}
 />
  • ์‹ ๊ณ ๊ธฐ๋Šฅ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ์‹œ, ํ•„์š”ํ•œ ์ฑ„ํŒ… ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์ฃผ๋Š” ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

+) ์š•์„ค ํ•„ํ„ฐ๋ง ๋ฐฑ์—”๋“œ ๊ตฌํ˜„ ์™„๋ฃŒ



โœจย ๋ฒค ๋‹นํ•œ ์‚ฌ์šฉ์ž ์ ‘๊ทผ ์ œํ•œํ•˜๊ธฐ & join ํ›„ ์‚ฌ์šฉ์ž ์ธ์ฆ

useEffect(() => {
    socket.on('connect_error', data => {
      if (data.message === 'invalid user') {
        socket.close();
        navigate('/');
      }
    });
    return () => {
      socket.close();
    };
  }, []);
  • ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ data ์˜ ๊ฐ’์ด invalid user (๋ฐฑ์—”๋“œ์—์„œ ์ง€์ •) ์ผ ๊ฒฝ์šฐ socketclose() ๋ฅผ ํ†ตํ•ด ํ†ต์‹ ์„ ์ œํ•œํ•œ๋‹ค. ์ด ํ›„ navigate๋ฅผ ํ†ตํ•ด์„œ main ์œผ๋กœ ์ด๋™์‹œํ‚จ๋‹ค.
  • ๊ทธ ์™ธ์˜ ๊ฒฝ์šฐ socket.close() ๋ฅผ ํ†ตํ•ด ํ†ต์‹ ์„ ๋Š์„ ์ˆ˜ ์žˆ๋‹ค โ† ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ธ ๊ฒƒ ๊ฐ™๋‹ค.๐Ÿง ๋งŒ์•ฝ ์ด ๊ตฌ๋ฌธ์ด ์—†๋‹ค๋ฉด, ์œ ์ €๊ฐ€ ๋’ค๋กœ๊ฐ€๊ธฐ๋ฅผ ํ†ตํ•ด ์ฑ„ํŒ…๋ฐฉ์—์„œ ๋ฒ—์–ด๋‚˜๋„ ๋‹ค๋ฅธ ์œ ์ €์˜ ์ฑ„ํŒ…๋ฐฉ์—์„œ๋Š” ๋– ๋‚˜๊ฐ„ ์œ ์ €์˜ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์—†์—ˆ๋‹ค.



โœจย ์ฑ„ํŒ… ์ž…๋ ฅ ๊ธ€์ž์ˆ˜ ์ œํ•œ & ์ค„๋ฐ”๊ฟˆ์ฒ˜๋ฆฌ

maxLength

<InputBox>
        <ChatInput
          type="text"
          maxLength={45}
          value={message}
          placeholder="๋ฉ”์„ธ์ง€ ์ž…๋ ฅ"
          onChange={e => setMessage(e.target.value)}
          onKeyDown={e =>
            e.key === 'Enter' && e.nativeEvent.isComposing === false
              ? sendMessage(e)
              : null
          }
        />
  • type์€ โ€˜textโ€™ ์—ฌ์•ผ ํ•œ๋‹ค.
  • maxLength ๋Š” ๋ฆฌ์•กํŠธ ๋ฌธ๋ฒ•์ƒ โ€˜Lโ€™๋Œ€๋ฌธ์ž ์ฃผ์˜ ! (JS์—์„œ๋Š” maxlength)
    Javascript_ex. ) <input type="text" name="usrname" maxlength="10">
  • ๋‹จ {} ์•ˆ์— ์ˆซ์ž๋ฅผ ๋„ฃ์–ด์ฃผ๊ธฐ !! =โ€โ€ ํ˜•์‹์œผ๋กœ๋Š” ๋˜์ง€ ์•Š์•˜๋‹ค.



โœจ ์˜๋ฌธ ์ธํ’‹์ฐฝ์—์„œ ๋ฒ—์–ด๋‚จ ํ•ด๊ฒฐํ•˜๊ธฐ


๐Ÿ“Œ Tip
โ—๏ธ import styled from 'styled-components/macro';
โ†’ ๊ฐœ๋ฐœ์ž๋„๊ตฌ UI๋ฅผ ํ™•์ธํ•  ๋•Œ ์ž„์˜์˜ ํด๋ž˜์Šค๋ช…์„ ์›๋ž˜๋Œ€๋กœ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ !






โœ๏ธย ์ถ”๊ฐ€ ๊ณ ๋ คํ•  ์‚ฌํ•ญ

1) ์‚ฌ์šฉ์ž ์ธ์ฆ โ†’ socket.io auth

  • socket io ์—์„œ ์ œ๊ณตํ•˜๋Š” auth ๊ธฐ๋Šฅ
  • ์œ ์ € connect๊ฐ€ ๋˜๋ฉด ์œ ํšจํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๋งž๋Š”์ง€ ์ฒดํฌํ•  ์ˆ˜ ์žˆ์Œ (?)
  • ์œ ์ €๊ฐ€ ์—ด๊ฒฐ ๋˜์—ˆ์„ ๋•Œ, ํ† ํฐ์„ ๋„ฃ์–ด์„œ ์„œ๋ฒ„๋‹จ์œผ๋กœ ๋ณด๋ƒ„์œผ๋กœ์จ ์„œ๋ฒ„์—์„œ ๊ทธ ํ† ํฐ์„ ํ•œ๋ฒˆ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.
  • socket.io ์ž์ฒด ์ธ์ฆ๊ธฐ์ฆ์ด ์žˆ์Œ (์ง€์›) auth ํ† ํฐ ๊ฐ’์„ ํ™•์ธ ํ•ด์„œ ์œ ํšจํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด ๊ฐ•์ œ ํ‡ด์žฅdisconnect ์‹œํ‚ด


2) ๋น„๋กœ๊ทธ์ธ ์ฑ„ํŒ…๋ฐฉ ์ž…์žฅ ์ œ์–ดํ•˜๊ธฐ

  • ๋น„๋กœ๊ทธ์ธ ์ฑ„ํŒ…๋ฐฉ ์ž…์žฅ ์ œ์–ด โ†’ ํด๋ฆญํ•  ๋•Œ ์ž…์žฅํ•˜๋Š” ๊ฒƒ๊ณผย  ์ˆ˜๋™์œผ๋กœ url ์ž…๋ ฅ์‹œ ์ž…์žฅํ•˜๋Š”๊ฒƒ ๋ชจ๋‘ ์ œ์–ด ํ•„์š”
  • ํ›„์ž์˜ ๊ฒฝ์šฐ GET ์š”์ฒญ ํ˜น์€ redux๋กœ ์œ ์ € ์ •๋ณด ํŒŒ์•… ํ›„ ์ปดํฌ๋„ŒํŠธ UI๋ฅผ ๋ฐ”๋กœ return ์‹œํ‚ค๊ธฐ




๐Ÿ“Œ ๋ฐฑ์—”๋“œ ํ†ต์‹  ์ฝ”๋“œ

import os
import socketio

import django
from .mongodb import save_message
from django.conf import settings

from cores.utils import censor_text

# Django ๊ธฐ๋ณธ์ƒ์„ฑ ํŒŒ์ผ์ด ์•„๋‹Œ ๊ฒฝ์šฐ ๋ฐ‘์—์„œ Django models๋ฅผ ์ž„ํฌํŠธํ•˜๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒํ•จ 
# Django models ์ž„ํฌํŠธ ํ•˜๊ธฐ ์ „์— django.setup() ํ•ด์ค˜์•ผ ์—๋Ÿฌ ์•ˆ ๋‚จ
django.setup()

from cores.models import UserStatus
from users.models import User

# Django ํ™˜๊ฒฝ์„ค์ • ํŒŒ์ผ ๋กœ๋“œ
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "togedog_dj.settings")

# ์†Œ์ผ“.io ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ, ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด eventlet ํ™œ์šฉํ•˜๋ฉฐ, cors๋Š” ํ˜„์žฌ ๋ชจ๋‘ ํ—ˆ์šฉ
# logger=True๋กœ ์„ค์ •ํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ ๋กœ๊ทธ๋ฅผ ํ„ฐ๋ฏธ๋„์— ์ถœ๋ ฅํ•จ
sio = socketio.Server(async_mode='eventlet', cors_allowed_origins='*', logger=True)

# ์ฑ„ํŒ…๋ฐฉ ์œ ์ € ์ž…์žฅ/ํ‡ด์žฅ ๋ฉ”์‹œ์ง€ ์šฉ ๋นˆ ๋”•์…”๋„ˆ๋ฆฌ ์ƒ์„ฑ
users = {}

# sio.event: connect์™€ disconnect๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์ ‘์†ํ•˜๊ฑฐ๋‚˜ ์ ‘์†์„ ๋Š์„ ๋•Œ ๋ฐœ์ƒ
# ์ด๋ฒคํŠธ ๋ช…์œผ๋กœ connect, message, disconnect ๋“ฑ์€ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜๋œ ์ด๋ฒคํŠธ๋กœ, python-socketio์—์„œ ์ •ํ•ด์ง„ ๋Œ€๋กœ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
# @sio.event๋Š” ๋ฐ‘์—์„œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ฐ›์€ ํ•จ์ˆ˜๋ช…(def ํ•จ์ˆ˜๋ช…)์„ ์ด๋ฒคํŠธ๋กœ ๋ฐ›์œผ๋ฉฐ @sio.on('์ด๋ฒคํŠธ๋ช…')๋Š” ์ธ์ž ๊ฐ’์œผ๋กœ ์ด๋ฒคํŠธ๋ช…์„ ๋ฐ›์Œ
# ๋”ฐ๋ผ์„œ @sio.on์œผ๋กœ๋Š” ํ•จ์ˆ˜๋ช…์œผ๋กœ ์“ฐ์ง€ ๋ชปํ•˜๋Š” ํ•œ๊ธ€์ด๋‚˜ ๊ณต๋ฐฑ์ด ๋“ค์–ด๊ฐ„ ๋ฌธ์ž์—ด์„ ์ด๋ฒคํŠธ๋ช…์œผ๋กœ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ์Œ
# sio.emit์•ˆ์˜ ์ธ์ž์ธ to์™€ room์€ ์™„์ „ํžˆ ๋˜‘๊ฐ™์Œ, ์•„๋ฌด๊ฑฐ๋‚˜ ์จ๋„ ๋จ, ํŠน์ • room์œผ๋กœ๋งŒ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉ

@sio.event
def connect(sid, environ, auth):
    '''
    socket.io ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ๋˜๋ฉด ์•„๋ž˜ ์ฝ”๋“œ ์‹คํ–‰
    auth์˜ ์‚ฌ์šฉ์žid๊ฐ’์œผ๋กœ ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€์•Š๊ฑฐ๋‚˜ ์ฐจ๋‹จ์œ ์ €์ด๋ฉด connect_error๋กœ emit
    emit ๋˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ชฝ์—์„œ ๊ฐ•์ œ๋กœ ์—ฐ๊ฒฐํ•ด์ œ ์ฒ˜๋ฆฌ
    '''
    print('connected ', sid) # 'connected sid๊ฐ’' ํ”„๋ฆฐํŠธ
    print(auth) # ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ auth๊ฐ’ ํ”„๋ฆฐํŠธ

    user = User.objects.filter(id=auth["userId"]) 
    if not user or user.get().status == UserStatus.BANNED.value:
        sio.emit('connect_error', {'message': 'invalid user'})

@sio.on('join')
def handle_join(sid, data):
    '''
    ์œ ์ €๊ฐ€ ์ฑ„ํŒ…๋ฐฉ์— ์ž…์žฅํ•˜๋ฉด ์•„๋ž˜ ์ฝ”๋“œ ์‹คํ–‰
    ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฑ„ํŒ…๋ฐฉ ์ž…์žฅ์‹œ connect ์ด๋ฒคํŠธ๊ฐ€ ๋จผ์ € ๋ฐœ์ƒํ•˜๊ณ  ๋ฐ”๋กœ join ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒ๋จ
    data๋ฅผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด๋กœ ๋ฐ›์œผ๋ฉด ํŒŒ์ด์ฌ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์‚ฌ์šฉ๊ฐ€๋Šฅ
    data์—๋Š” ๋ฐฉ ๋ฒˆํ˜ธ์™€ ์‚ฌ์šฉ์ž ๋‹‰๋„ค์ž„์ด ํฌํ•จ๋จ
    ์ฑ„ํŒ…๋ฐฉ์— ์ ‘์†ํ•œ ์‚ฌ์šฉ์ž์˜ sid๊ฐ’์„ key๋กœ ํ•˜๊ณ , data ๊ฐ์ฒด ์ „์ฒด๊ฐ€ value๋กœ users ๋”•์…”๋„ˆ๋ฆฌ์— ์ €์žฅ    
    '''
    users[sid] = data
    sio.enter_room(sid, room=data['room'])
    
    sio.emit(
        'add_message', 
        {
            "user_nickname": 'ํ•จ๊ป˜ํ•˜๊ฐœ ๊ด€๋ฆฌ์ž', 
            "text": f"{data['nickname']}๋‹˜์ด ๋“ค์–ด์™”์–ด์š”."
        }, 
        to=data['room']
    )

@sio.on('send_message')
def handle_send_message(sid, message, nickname, room, currentTime, userMbti, userImage, userId):
    '''
    ๋ฐ‘์—์„œ emitํ•  data ๊ฐ์ฒด ์ƒ์„ฑ
    censor_text(): ์š•์„ค ํ•„ํ„ฐ๋ง ํ•จ์ˆ˜, ์š•์„ค ๋ถ€๋ถ„์ด ์žˆ์œผ๋ฉด *์ž๋กœ ์ˆ˜์ •ํ•œ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•จ
    save_message(): MongoDB์— ๋ฉ”์‹œ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ณ ์œ  ObjectId(24์ž๋ฆฌ ๋ฌธ์ž์—ด)๋ฐ˜ํ™˜
    ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด add_message๋กœ emit
    '''
    data = {
        "user_nickname": nickname,
        "user_id"      : userId,
        "user_mbti"    : userMbti,
        "user_image"   : userImage,
        "text"         : censor_text(message),
        "message_id"   : save_message(message, nickname, userId, room),
        "time"         : currentTime,
    }
    sio.emit('add_message', data, to=room)

@sio.event
def disconnect(sid):
    '''
    socket.io ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ ํ•ด์ œ๋˜๋ฉด ์•„๋ž˜ ์ฝ”๋“œ ์‹คํ–‰
    leave_username: ์—ฐ๊ฒฐ ํ•ด์ œ๋œ sid๊ฐ’์˜ ์œ ์ € ๋‹‰๋„ค์ž„
    leave_room_number: ์—ฐ๊ฒฐ ํ•ด์ œ๋œ sid๊ฐ’์˜ ๋ฐฉ ๋ฒˆํ˜ธ
    ํ‡ด์žฅํ•œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‹ด์•„ add_message๋กœ emit
    users ๋”•์…”๋„ˆ๋ฆฌ์—์„œ ํ•ด๋‹น sid๊ฐ’์˜ ๋ฐ์ดํ„ฐ ์‚ญ์ œ
    '''
    leave_username = users[sid].get('nickname')
    leave_room_number = users[sid].get('room')
    sio.emit(
        'add_message', 
        {
            "user_nickname": 'ํ•จ๊ป˜ํ•˜๊ฐœ ๊ด€๋ฆฌ์ž', 
            "text": f"{leave_username}๋‹˜์ด ํ‡ด์žฅํ•˜์…จ์–ด์š”."
        }, 
        to=leave_room_number
    )
    del users[sid]




์ด์Šˆํ•ด๊ฒฐํ•˜๊ธฐ

๐Ÿ”จ socket id ๋ฌดํ•œ ์ƒ์„ฑ ์ด์Šˆ

const ENDPOINT = 'localhost:3000'; // 1)

/* ์ž…์žฅ์‹œ ๋‹‰๋„ค์ž„๊ณผ ๋ฐฉ์„ ์ „๋‹ฌ */
  useEffect(() => {
    socket = io(ENDPOINT); // 2)
    socket.emit('์ž…์žฅ', { nickname, room });
    console.log('1) ์ž…์žฅ์‹œ nickname, room ๋„˜๊ธฐ๊ธฐ');
  }, [ENDPOINT]);

โ—๏ธ socket = io(ENDPOINT) ์œ„์น˜ ๋ณ€๊ฒฝ

1) ์ „์—ญ์— ENDPOINT๋ฅผ ์„ค์ •ํ•ด์ฃผ๊ณ 
2) useEffect์— socket = io(ENDPOINT)๋ฅผ ์ง€์ •ํ•ด์ฃผ์—ˆ๋‹ค.
โ†’ ์›๋ž˜ ์ด ์ฝ”๋“œ๊ฐ€ 1) ๋ฐ”๋กœ ์•„๋ž˜ ์ „์—ญ์— ์œ„์น˜ํ•ด์žˆ์—ˆ๋Š”๋ฐ ์ด ๋•Œ๋ฌธ์— ๋งค๋ฒˆ ์žฌ๋ Œ๋”๋ง์ด ๋˜๋ฉด์„œ ์ฝ”๋“œ๊ฐ€ ๊ณ„์†ํ•ด์„œ ์‹คํ–‰, ๊ธ€์ž๋ฅผ ์ž…๋ ฅํ•˜๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ๊ณ„์†ํ•ด์„œ socket id๊ฐ€ ์ƒˆ๋กœ ์ƒ์„ฑ๋˜๋Š” ์ด์Šˆ๊ฐ€ ์žˆ์—ˆ๋‹ค.
์œ„์น˜๋ฅผ useEffect ๋ฌธ์•ˆ์— ์ž‘์„ฑ, deps๊ฐ’์œผ๋กœ ENDPOINT๋ฅผ ์ง€์ •ํ•ด์คŒ์œผ๋กœ์จ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.



๐Ÿ”จย ํ˜„์žฌ ์‹œ๊ฐ„ ๊ฐ€์ ธ์˜ค๊ธฐ

const [currentTime, SetCurrentTime] = useState('');

/* ํ˜„์žฌ์‹œ๊ฐ„์„ ์–ป๋Š” ํ•จ์ˆ˜ */
  useEffect(() => {
    const date = new Date();
    const hours = date.getHours();
    const minutes = ('0' + date.getMinutes()).slice(-2); // 1)
    const currentHour = hours > 12 ? `์˜คํ›„ ${hours - 12}` : `์˜ค์ „ ${hours}`;

    SetCurrentTime(`${currentHour}:${minutes}`);
  }, [message]); // 2)

1) 1์˜์ž๋ฆฌ์ˆ˜ ๊ฐ€ ๋‚˜์˜ฌ ๊ฒฝ์šฐ ์•ž์— 0์„ ๋ถ™์ด๋„๋ก ์ž‘์„ฑ
2) ๋งŒ์•ฝ deps ๊ฐ’์œผ๋กœ messages๋ฅผ ๋„ฃ์œผ๋ฉด ์ฑ„ํŒ…๋ฐฉ์— ์ฑ„ํŒ…์„ ์น˜์ง€์•Š๊ณ  ๋จธ๋ฌผ๋Ÿฌ์žˆ๋Š” ์‹œ๊ฐ„์ž์ฒด๋ฅผ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ๋‹ค๊ฐ€ ์น˜๋Š” ์ˆœ๊ฐ„ ๊ทธ ์ด์ „ ์‹œ๊ฐ„์ด ํ‘œ๊ธฐ๋˜๋Š” ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒ๋œ๋‹ค.๐Ÿค”
โ†’ ๊ฐ„๋‹จํ•˜๊ฒŒ message๋กœ ๋ฐ”๊ฟ”์ฃผ๋ฉด์„œ ์ด์™€๊ฐ™์€ ์ด์Šˆ ํ•ด๊ฒฐ!



๐Ÿ”จย ๋ฆฌ๋•์Šค + ์ „๋‹ฌํ•˜๋Š” ๋ฐ์ดํ„ฐ์™€ ์ „๋‹ฌ๋œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์ด์Šˆ

  • redux์—์„œ initialstate ๊ฐ’์„ {}๋กœ ์ˆ˜์ •ํ•˜๋Š” ์ˆœ๊ฐ„ ๋ฐœ์ƒํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž… ํ˜•์‹์˜ (action, reducer ๋ถ€๋ถ„์—์„œ) ๋ฌธ์ œ๋ผ๊ณ  ์˜ˆ์ƒํ•˜๊ณ  ์‚ฝ์งˆ ์‹œ์ž‘๐Ÿฅฒ
  • ์ฐธ๊ณ ํ•œ ๋ธ”๋กœ๊ทธ โœจย โ†’ Object๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ Œ๋”๋ง ํ•˜๋ ค๊ณ  ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด๋‹ค.
    React - Objects are not valid as a React child ์—๋Ÿฌ ์ฒ˜๋ฆฌ

โ“ย ์—๋Ÿฌ์ฝ”๋“œ๋ฅผ ๊ฒ€์ƒ‰, ์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ์—์„œ ํ‚ค์›Œ๋“œ ๋„์›€์„ ์–ป์—ˆ๋‹ค. UI ๋ Œ๋”๋ง์ด ๋ฌธ์ œ๋‹ค? ๋ Œ๋”๋ง์ด ๋ฐœ์ƒ๋˜๋Š” ๋ถ€๋ถ„์„ ์ฒดํฌํ•ด์•ผํ• ๊นŒ?

// titleBar

...
const roomName: any = useSelector<any>(state => state.chat.title); // <- title ๋กœ ํ•œ๋ฒˆ ๋” ์ง„์ž…
...

return (
    <TitleBarContainer>
      <BackBox onClick={goToChatList}>
        <GoBackIcon src={ArrowLeft} />
        <GoBackText>์ฑ„ํŒ…๋ชฉ๋ก</GoBackText>
      </BackBox>
      <Title>{roomName}</Title>
    </TitleBarContainer>
  );

โ—๏ธ state.chat ๋ถ€๋ถ„์—์„œ title ์„ ํ•œ ๋ฒˆ๋” ๋“ค์–ด๊ฐ€์„œ ํ•ด๊ฒฐํ–ˆ๋‹ค. ์ด์œ ๋Š”๐Ÿง !?

const initialState = { // 1)
  id: 0,
  title: '',
};

const postsReducer = (prevState = initialState, action) => {
  switch (action.type) {
    case 'SET_ROOM':
      return { ...prevState, title: action.data };
    // return action.data;

    case 'SET_ROOM_ID':
      return { ...prevState, id: action.data };
    // return action.data;

    default:
      return prevState;
  }
};

export default postsReducer;
  • ์ด๋ถ€๋ถ„์„ โ€˜โ€™ ์—์„œ id์™€ title๋กœ ๋‚˜๋ˆ ์„œ ๊ฐ์ฒดํ˜•ํƒœ๋กœ ์„ค์ •ํ•ด ์ฃผ๋‹ˆ๊นŒ store์— ์ ‘๊ทผํ• ๋•Œ ๊ฐ์ฒด๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ UI ๋ Œ๋”๋ง์ด ๋˜๊ธฐ ๋•Œ๋ฌธ! ๐Ÿ˜€



๐Ÿ”จ ์ฐจ๋‹จ ์œ ์ € ์ฒ˜๋ฆฌ

โ“ ์„œ๋ฒ„์ชฝ ์ฝ”๋“œ์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด emit ํ•˜๋ฉด ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋„ ํŠ•๊ธฐ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

# sio.emit("connect_error", {"message": "invalid user"})

 # ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ:
 #   useEffect(() => {
 #     socket.on('connect_error', data => {
 #       if (data.message === 'invalid user') {
 #         socket.close();
 #         navigate('/');
 #       }
 #     });
 #     return () => {
 #       socket.close();
 #     };
 #   }, []);
  • ์ด ๊ฒฝ์šฐ, ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋œ ์ฐฝ, ์ฐจ๋‹จ ์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธ๋œ ์ฐฝ ๋‘๊ฐœ๋ฅผ ๋™์‹œ์— ์ผœ๋‘๊ณ  ์ ‘์†ํ•  ๊ฒฝ์šฐ์—๋งŒ ๋ฐœ์ƒํ•˜๋Š” ๋“ฏ ํ•˜๋‹ค.๐Ÿง
  • ๋”ฐ๋ผ์„œ ์„œ๋ฒ„์ชฝ์—์„œ๋Š” emit์—†์ด False ๋ฐ˜ํ™˜๋งŒ ํ•˜๋ฉด ๊ถŒํ•œ์—†๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ฑ„ํŒ…๋ฐฉ์— ์ ‘์†ํ•  ์ˆ˜ ์—†๊ณ  ๋ฐ”๋กœ ์—ฐ๊ฒฐ์ด ํ•ด์ œ๋œ๋‹ค.
@sio.event
def connect(sid, environ, auth):
    '''
    socket.io ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ๋˜๋ฉด ์•„๋ž˜ ์ฝ”๋“œ ์‹คํ–‰
    auth์˜ ์‚ฌ์šฉ์žid๊ฐ’์œผ๋กœ ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€์•Š๊ฑฐ๋‚˜ ์ฐจ๋‹จ์œ ์ €์ด๋ฉด connect_error๋กœ emit
    emit ๋˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ชฝ์—์„œ ๊ฐ•์ œ๋กœ ์—ฐ๊ฒฐํ•ด์ œ ์ฒ˜๋ฆฌ
    '''
    user = User.objects.filter(id=auth["userId"])
    if not user.exists() or user.get().status == UserStatus.BANNED.value:
        return False 
  • ์ด๋ ‡๊ฒŒ ์„œ๋ฒ„๋‹จ ๋กœ์ง์„ ์ˆ˜์ •ํ•จ์œผ๋กœ์จ ํด๋ผ์ด์–ธํŠธ์ชฝ socket.on ๋กœ์ง์€ ์ง€์›Œ๋„ ์ž˜ ์ž‘๋™ํ•œ๋‹ค!
    (๋‹ค๋งŒ ์ด ๊ฒฝ์šฐ, ์ฐจ๋‹จ ์œ ์ €๋ฅผ homeํ™”๋ฉด์œผ๋กœ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ฑ„ํŒ… ํ™”๋ฉด๋‚ด์— ๋จธ๋ฌผ์ง€๋งŒ ์•„๋ฌด๋Ÿฐ ํ–‰๋™๋„ ํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜๋Š” ๊ฒƒ ๐Ÿ˜€)




io์™€ socket

โœ”๏ธ ์ดˆ๊ธฐ ์ž…์žฅ ๋ฉ”์„ธ์ง€ ์ „์†ก์‹œ io์™€ socket ์ฐจ์ด

// 1)
socket.on('join', ({ nickname, room }) => {
    socket.join(room);

    socket.emit('add_message', { // 1)-1
      user: 'ํ•จ๊ป˜ํ•˜๊ฐœ ๊ด€๋ฆฌ์ž',
      text: `${nickname} ๋‹˜์ด ๋“ค์–ด์™”์–ด์š”.`,
    });

    socket.broadcast.to(room).emit('add_message', {
      user: 'ํ•จ๊ป˜ํ•˜๊ฐœ ๊ด€๋ฆฌ์ž',
      text: `${nickname} ๋‹˜์ด ๋“ค์–ด์˜ค์…จ์Šต๋‹ˆ๋‹ค!`,
    });


// 2)
socket.on('join', ({ nickname, room }) => {
    socket.join(room);

    io.to(room).emit('add_message', { // 2)-1
      user: 'ํ•จ๊ป˜ํ•˜๊ฐœ ๊ด€๋ฆฌ์ž',
      text: `${nickname} ๋‹˜์ด ๋“ค์–ด์™”์–ด์š”.`,
    });
  });

1), 2) ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•  ๊ฒฝ์šฐ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž˜ ๋œฌ๋‹ค.

ํ•˜์ง€๋งŒ 1)-1์—์„œ socket๋Œ€์‹  2)-1 ์ฒ˜๋Ÿผ io.to(room).emit(โ€ฆ) ๋กœ ์ž‘์„ฑํ•˜๋ฉด

๋ฉ”์„ธ์ง€๊ฐ€ ์ค‘๋ณต๋˜์–ด์„œ ์ถœ๋ ฅ๋˜๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•œ๋‹ค. โ†’ ํ•˜์ง€๋งŒ ์„œ๋ฒ„๋‹จ์—์„œ room ๊ฐ’์€ ํ•„์š”ํ•˜๊ธฐ๋•Œ๋ฌธ์— io.to(room)ํ˜•์‹์„ ์จ์•ผํ•˜๋ฏ€๋กœ 2)์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜๋˜, broadcast๋ฅผ ์‚ญ์ œํ•ด์„œ ์œ„์™€๊ฐ™์ด 2)๋กœ ์‚ฌ์šฉ!



โœ๏ธ ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ตฌํ˜„์„ ๋งˆ์น˜๋ฉฐ

  • ์ฒ˜์Œ ์‚ฌ์šฉํ•ด๋ณด๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ์ฒ˜์Œ ์ ‘ํ•ด๋ณด๋Š” ๊ธฐ๋Šฅ ์ง€์‹์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋ ค์› ๋‹ค. ์ƒˆ๋กœ์šด ๊ฒƒ์„ ๋ฐฐ์›Œ ๋ณผ ์ˆ˜ ์žˆ๋‹ค ๋ผ๋Š” ์„ค๋ ˆ์ž„๊ณผ ๊ธฐ๋Œ€๊ฐ๋งŒ ๊ฐ–๊ณ  ๋„์ „ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์•˜์ง€๋งŒ, ํ•จ๊ป˜ ๋งŒ๋“ค์–ด๋‚˜๊ฐ€๋Š” ํŒ€ ํ”„๋กœ์ ํŠธ์˜€๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋Š์ •๋„์˜ ์‹œ๊ฐ„์„ ๊ฐ€์ง€๊ณ  ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์„์ง€์—๋Œ€ํ•ด ๊ฐ์ด ์žกํžˆ์งˆ ์•Š์•˜๋‹ค.๐Ÿ˜…
  • ํ•˜์ง€๋งŒ ์œ ํŠœ๋ธŒ ๊ฐ•์˜, ๊ตฌ๊ธ€๋ง, ๋…ธ๋งˆ๋“œ์ฝ”๋” ํด๋ก  ๊ฐ•์˜๋“ฑ์„ ํ•˜๋‚˜ํ•˜๋‚˜ ์ฐจ๊ทผ์ฐจ๊ทผ webSocket โ†’ socketIO , ๊ทธ๋ฆฌ๊ณ , JS โ†’ React๋กœ ์ ์šฉํ•ด ๊ฐ€๋ฉด์„œ ์†Œ์ผ“ ํ†ต์‹ ์— ๋Œ€ํ•ด ๊ฐ์„ ์žก์„ ์ˆ˜ ์žˆ์—ˆ๊ณ  ์ง์ ‘ ๋ˆˆ์œผ๋กœ ๋™์ž‘์„ ํ™•์ธํ•ด ๊ฐ€๋ฉด์„œ ์—๋Ÿฌ์‚ฌํ•ญ๋“ค์„ ํ•ด๊ฒฐํ•ด ๊ฐ€๋Š” ๊ณผ์ •์ด ๋ฌด์ฒ™ ์žฌ๋ฏธ์žˆ์—ˆ๋‹ค.๐Ÿ˜„โœจ
  • ์ฒ˜์Œ์œผ๋กœ ์ง์ ‘ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ํ†ต์‹ ์„ ๊ตฌํ˜„ํ•ด ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฒฝํ—˜์ด๊ธฐ๋„ ํ–ˆ๋‹ค. :) !!

profile
๊ธฐ์–ต๋ณด๋‹จ ๊ธฐ๋ก์„ โœจ

0๊ฐœ์˜ ๋Œ“๊ธ€