프로젝트에 웹소켓을 도입하기로 했다.
useContext를 사용하는 방식을 읽고 내가 원한 방식이어서 useContext로 세팅했다.
우리 프로젝트에서 웹소켓의 역할은 데이터의 변동을 알려주는 것이다.
- 데이터의 변동 알람을 수신 받음 => 데이터 요청 API를 날림 식으로 웹소켓과 소통한다.
import { useAppSelector } from '@app/hooks';
import { REACT_APP_SOCKET_SERVER } from '@env';
import useAuth from '@hooks/useAuth';
import { Client } from '@stomp/stompjs';
import React, { useRef, useState, useEffect } from 'react';
const WebSocketContext = React.createContext<any>(null);
export { WebSocketContext };
export default function ({ children }: { children: React.ReactNode }) {
const { getAccessToken } = useAuth();
const token = useAppSelector(state => state.auth.accessToken?.token);
const client = useRef<Client>();
useEffect(() => {
const stompConfig = {
brokerURL: `ws${REACT_APP_SOCKET_SERVER}/ws-meetings?token=${token}`,
debug: (frame: any) => console.log(frame),
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
forceBinaryWSFrames: true,
appendMissingNULLonIncoming: true,
};
if (!client.current && token) {
client.current = new Client(stompConfig);
client.current.onConnect = () => {
console.log(`connected to ${stompConfig.brokerURL}`);
};
client.current.onDisconnect = error => {
console.log(`disconnected to ${stompConfig.brokerURL}`);
console.log(error);
};
client.current.onDisconnect = error => {
console.log(`error to ${stompConfig.brokerURL}`);
console.log(error);
};
}
}, [token]);
return (
<WebSocketContext.Provider value={client}>
{children}
</WebSocketContext.Provider>
);
}
이런식으로 connect시의 활동을 작성하고 업데이트 메세지에 따라 상태가 변하도록 리덕스를 작성해줬다.
import { createSlice } from '@reduxjs/toolkit';
interface ISocket {
message: {
messageType: string;
data: object;
};
votingUpdate: boolean;
meetingUpdate: boolean;
memberUpdate: boolean;
onlineUserUpdate: boolean;
onlineUserList: number[];
}
const initialState = {
message: {
messageType: '',
data: {},
},
votingUpdate: false,
meetingUpdate: false,
onlineUserUpdate: false,
memberUpdate: false,
onlineUserList: [],
};
const socketSlice = createSlice({
name: 'socket',
initialState: initialState as unknown as ISocket,
reducers: {
onMessage: (state, action) => {
const message = action.payload;
const { messageType, data } = message;
console.log(`messageType${messageType}`);
console.log(data);
if (messageType !== 'RESOURCE_UPDATED_EVENT') {
state.onlineUserUpdate = true;
state.memberUpdate = true;
state.onlineUserList = data.userIds;
return;
}
if (messageType === 'RESOURCE_UPDATED_EVENT')
switch (data.meetingResourceType) {
case 'MEETING_VOTING':
state.votingUpdate = true;
break;
case 'SUBSCRIBE_MEETING_EVENT':
state.meetingUpdate = true;
break;
case 'UNSUBSCRIBE_MEETING_EVENT':
state.meetingUpdate = true;
break;
case 'MEETING_MEMBERS':
state.memberUpdate = true;
break;
default:
break;
}
},
setVotingUpdateEnd: state => {
state.votingUpdate = false;
},
setMeetingUpdateEnd: state => {
state.meetingUpdate = false;
},
setMemberUpdateEnd: state => {
state.memberUpdate = false;
},
setOnlineUserUpdateEnd: state => {
state.onlineUserUpdate = false;
},
},
});
export const {
onMessage,
setVotingUpdateEnd,
setMeetingUpdateEnd,
setMemberUpdateEnd,
setOnlineUserUpdateEnd,
} = socketSlice.actions;
export default socketSlice.reducer;
소켓에서 메세지를 받으면 상태를 업데이트 => 업데이트가 끝난후엔 ~END함수를 실행해서 다시 상태를 바꿔줘 다음에도 변화를 감지할 수 있게 한다.
const connect = () => {
client.onConnect = () => {
log('log', '성공');
getMeetingData();
const subscription = client.subscribe(
`/sub/meetings/${meetingId}`,
async frame => {
log('log', frame);
const data = await JSON.parse(frame.body);
dispatch(onMessage(data));
},
);
};
client.activate();
};
미팅룸에 들어오면 실행하는 connect함수 미팅룸 스크린에 들어오면 소켓연결 요청과 구독을 동시에한다. 소켓에서 메세지가 오면 리덕스가 실행되고 => 리덕스의 상태를 받아오는 함수들이 실행된다.