본문 바로가기

STUDY/React

React | socket.io를 활용한 채팅 (+ Node.js, redux)

아직 완성은 아니지만 정리하기 위해 작성해봅니다.

 

+) 참고

 

[Node.js] 웹 소켓으로 실시간 랜덤 채팅 구현 중 메시지 중복 버그 해결과정 (WebSocket Random Chat - clients rendering duplicate message)

정말 오랜만에 블로그 글을 작성하는 것 같다. 바닐라코딩에서 서버쪽 공부를 시작하면서 할 것도 엄청 많고 과제에 허덕이다보니 도저히 블로그 글을 쓸 여유가 안생겼었다. 이번에 웹소켓을 이용해서 랜덤 채팅..

im-developer.tistory.com

 

 

0. create-react-app을 이용해 기본 뼈대 만들기

2020/01/02 - [STUDY/React.js] - React | Create React App을 이용해 React 시작하기

그 후,  server폴더 생성

 

1. socket.io 설치

socket.io는 브라우저와 서버간 실시간 통신을 가능하게 해주는 라이브러리입니다.

$ npm install socket.io

 

 

2. 서버 생성

http서버로 생성하는 것도 가능하나 저는 express를 이용하여 서버를 생성해주었습니다.

앞서 생성해둔 server폴더 안에 작성

//chat_server.js

const express = require('express');
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
const port = process.env.PORT || 3005 ;

server.listen(port, () => { console.log(`Listening on port ${port}`) });

 

 

3. client 작성

우선 socket.io-client를 설치해줍니다.

$ npm install socket.io-client

3-1. redux를 사용하기 위해 react-redux설치

$ npm install react-redux

 

 

4. Action작성

4-1. action types

굳이 나누지 않아도 되지만 앱이 커질경우 이렇게 type들만 별도 모듈로 관리하는게 좋다고 합니다...!

// src/redux/constants/actionTypes.js

export const SEND_CHAT = "SEND_CHAT";
export const RECEIVE_CHAT = "RECEIVE_CHAT";

 

4-2. action 생산자 작성

// src/redux/actions/actions.js

import * as type from '../constants/actionTypes';

export const sendChat = () => {
    return {
        type: type.SEND_CHAT
    }
}

export const receiveChat = (data) => {
    return {
        type: type.RECEIVE_CHAT,
        data
    }
}

 

 

 

5. Reducer 작성

Reducer에서는 상태(state)를 관리하게 됩니다.

여러가지 리듀서를 작성한 후 combineReducers로 리듀서들을 합쳐주면 상태를 좀 더 나눠서 관리할 수 있게됩니다.

 

앞으로 리듀서를 더 추가할 예정이라 미리 combineReducers를 사용해주었습니다.

// src/redux/reducers/reducers.js

import {combineReducers} from 'redux';
import * as type from '../constants/actionTypes';

const chatStates = {
    chatList: [],
    socketId: null
};

const chatReducer = (state = chatStates, action) => {
    switch(action.type){
        case type.MY_SOCKET_ID:
            return { ...state, socketId: action.socketId };
        case type.RECEIVE_CHAT:
            let newChatList = state.chatList.slice();
            newChatList.push(action.data);
            return { ...state, chatList: newChatList };
        case type.CLEAR_CHAT:
            return {...state, chatList: []}
        default:
            return state;
    }
}


const rootReducer = combineReducers({chatReducer});

export default rootReducer;
{...state, ...newState} == Object.assign(state, { newState })
...state를 앞에 쓰는 것이 이전의 state값을 복제한 후 state를 변화시키는 것

 

 

 

6. Store작성

리듀서는 여러 개를 작성할 수 있지만 스토어는 하나로 작성해야 합니다. (분리할 수 있다고는 함)

logger는 액션이 실행되고 state값이 바뀌는 부분을 콘솔창에 보여주는 편리한 미들웨어입니다. 사용하세요!

// src/reducer/store.js

import {createStore, applyMiddleware} from 'redux';
import logger from 'redux-logger';
import rootReducer from './reduecers/reducers';

const store = createStore(rootReducer, applyMiddleware(logger));

export default store;

 

5-1. 스토어 연결

//src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import store from './redux/store';
import App from '../src/containers/App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>, document.getElementById('root')
);

serviceWorker.unregister();

 

 

7. socket 통신해보기!

 

드디어!

//server/chat_server.js

const express = require('express');
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
const port = process.env.PORT || 3005 ;

server.listen(port, () => { console.log(`Listening on port ${port}`) });

io.on('connection', socket => {
    console.log("연결된 socketID : ", socket.id);
    io.to(socket.id).emit('my socket id',{socketId: socket.id});

    socket.on('enter chatroom', () => {
        console.log("누군가가 입장함");
        socket.broadcast.emit('receive chat', {type: "alert", chat: "누군가가 입장하였습니다.", regDate: Date.now()});
    })

    socket.on('send chat', data => {
        console.log(`${socket.id} : ${data.chat}`);
        io.emit('receive chat', data);
    })

    socket.on('leave chatroom', data => {
        console.log('leave chatroom ', data);
        socket.broadcast.emit('receive chat', {type: "alert", chat: "누군가가 퇴장하였습니다.", regDate: Date.now()});
    })
   
})

 

 

//containers/App.js

import { connect } from  'react-redux';
import * as action from '../redux/actions/actions';
import io from 'socket.io-client';
import App from '../../src/App';

const socket = io.connect("http://localhost:3005");

const socketSubscribe = dispatch => {
    socket.on('my socket id', (data) => {
        console.log('mySocketID : ' , data);
        dispatch(action.mySocketId(data.socketId));
    });
    socket.on('receive chat', (data) => {
        console.log("App.js Socket(receive chat) ", data);
        dispatch(action.receiveChat(data));
    });
}

const mapStateToProps = state => {
    console.log("containers/App.js mapStateToProps ", state);
    return state;
};

const mapDispatchToProps = dispatch => {
    socketSubscribe(dispatch);
    return {
        enterChatroom: () => {
            socket.emit('enter chatroom');
        },
        leaveChatroom: () => {
            socket.emit('leave chatroom');
        },
        sendChat: (chat) => {
            socket.emit('send chat', {type: "msg", socketId: socket.id, chat: chat, regDate: Date.now()});
        },
        clearChat: () => {
            dispatch(action.clearChat());
        }
    };
}


export default connect(mapStateToProps, mapDispatchToProps)(App);

 

 

//App.js

import React from 'react';
import {HashRouter as Router, Route} from 'react-router-dom';

import Home from './components/Home';
import Chat from './components/chat/Chat';


function App({chatReducer,  mySocketId, enterChatroom, leaveChatroom, sendChat, clearChat}) {
  console.log("src/App.js ", chatReducer);
  return (
    <Router>
      <Route path="/" exact component={Home}></Route>
      <Route path="/chat/:id" 
        render={props => <Chat chatReducer={chatReducer}
                               mySocketId={mySocketId}
                               leaveChatroom={leaveChatroom} enterChatroom={enterChatroom} 
                               sendChat={sendChat}
                               clearChat={clearChat} />} />
    </Router>
  );
}

export default App;

 

 

 

 

 

 

Socket.IO — Docs

What Socket.IO isSocket.IO is a library that enables real-time, bidirectional and event-based communication between the browser and the server. It consists of: a Node.js server: Source | API a Javasc

socket.io