socket.io 전용 훅스 만들기
웹소켓 : 실시간을 서버와 데이터를 주고받을 때 양방향으로 실시간 소통하는 방법. 한번만 연결을 맺어놓으면 계속 소통
Long Polling (롱 폴링) : 웹소켓 등장 전 양방향 통신 컨트롤
웹소켓 API 백엔드에서 전송
npm i socket.io-client@2 -> TypeORM이 아직 @3버전 정식지원X
*Socket.io는 한번 연결을 해놓으면 전역적인 특징을 띄기 때문에, 컴포넌트 연결에 복합함 발생할 수 있음. 리액트의 동적 성질과 어울리지 않는다* -> 하나의 컴포넌트에 종속되게 넣으면, 컴포넌트가 사라지면 연결도 끊기기에 공통 구성된 컴포넌트에 넣어줄 Hook 사용
부모에 연결해도 잘 안될경우 Hook에 넣어야함
useHooks.ts 에서 Hooks로 공통된 로직을 추출해낸다
React Hooks
함수형 컴포넌트에서 state 관리와 Life cycle을 다룰 수 있게 해주는 기술
useState(), useEffect(), useMemo(), useContext(), useRef(), useCallback()
화면이 들어가더라도 사용 가능
소켓 사용시 타입도 필요하기 때문에 설치해주기
npm i -D @type/socket.io-client =>(-D == --save-dev)
axios.get에서 https 등 반복주소는 변수로 빼주어야 실제 배포할때 변수를 실제주소로 한번에 바꿀 수 있다. 일반 문자열로 했을 경우 문제 발생
간단하게 socket.io 사용하기(서버세팅 완료상태 일경우)
const useSocket = () => {
const socket = io.connect(`${backUrl}`); //socket 모든 사람과 소통, 보안이슈, 범위조정!
//서버쪽에 hello라는 이벤트 이름으로 world라는 데이터를 전송
socket.emit('hello','world');
//서버에서 message 데이터가 오면 data변수명 일치하면 받는다
socket.on('message', (data) => {
console.log(data);
});
//소켓연결 끊는 함수
socket.disconnect();
};
프론트의 워크스페이스 계층과, socket의 namespace, 슬랙의 채널과 shcket의 room의 계층을 맞게 두어야 보안이슈를 해결할 수 있다. disconnect를 안하면, 여러번 받는 등 문제가 발생. 연결하고 끊는것 잘해줘야함!
동시에 여러 워크스페이스 들어가는 코드
const sockets = {}; //TS에러
const useSocket = (workspace) => {
//workspace 필수이기 때문에 없으면 return
if (!workspace){
return;
}
const sockets[workspace] = io.connect(`${backUrl}`); //socket 모든 사람과 소통, 보안이슈, 범위조정!
//서버쪽에 hello라는 이벤트 이름으로 world라는 데이터를 전송
sockets[workspace].emit('hello','world');
//서버에서 message 데이터가 오면 data변수명 일치하면 받는다
sockets[workspace].on('message', (data) => {
console.log(data);
});
//소켓연결 끊는 함수
const disonnect = sockets[workspace].disconnect();
return [sockets[workspace], disconnect];
};
Hook에서 소켓 사용하는 코드
const [socket, disconnect] = useSocket(workspace);
#TS에러# 빈객체나 빈배열에 타입 설정해줘야함
const sockets : { [key:string]: SocketIOClient.Socket} = {};
//문자열 키, 값 socket.io.client.socket
JS Scope : 함수 지역변수, 전역변수
const sockets: { [key: string]: Socket } = {};
const useSocket = (workspace?: string): [Socket | undefined, () => void] => { //소켓타입 지정
const disconnect = useCallback(() => {
if (workspace) {
sockets[workspace].disconnect();
delete sockets[workspace];
}
}, [workspace]);
if (!workspace) {
return [undefined, disconnect];
}
if (!sockets[workspace]) {
sockets[workspace] = io(`${backUrl}/ws-${workspace}`, {
transports: ['websocket'],
});
}
return [sockets[workspace], disconnect];
};
socket.io 이벤트 연결하기
로그인 실시간 전송
//Worksapce.index.ts
useEffect(() => {
if (channelData && userData && socket) {
console.log(socket);
socket.emit('login', { id: userData.id, channels: channelData.map((v) => v.id) });
}
}, [socket, channelData, userData]);
//socket 연결 끊기.
useEffect(() => {
return () => {
disconnect();
};
}, [workspace, disconnect]);
DMList - socket.io 연결하면, 실시간 접속 불 들어옴
useEffect(() => {
socket?.on('onlineList', (data: number[]) => {
setOnlineList(data);
});
return () => {
socket?.off('onlineList');
};
}, [socket]);
on 이벤트 리스너 정리하는(return) 함수 off
polling 폴링 에러(CORS) : HTTP요청 보내고, 웹소켓을 보내는 에러(익스플로어9의 웹소켓 미지원 확인)
연결할때 polling하지 말고, 웹소켓만 사용하라고 지시하는 함수
sockets[workspace] = io(`${backUrl}/ws-${workspace}`, {
transports: ['websocket'],
});
소켓 객체를 까보면, 내부정보들 확인할 수 있음!
웹소켓에서 지원하지 않는 기능을 socket.io 라이브러리가 제공해줌
WebSocket : 단순히 이벤트를 듣고 전달함
socket.io : 연결 실패시 fallback을 통해 알아서 다른방법으로 연결시도, room 개념으로 일부 클라이언트 전송하는 브로드캐스트 기능. 클라이언트 측에서 반드시 `socket.io-client` 라이브러리 이용해야함
콘솔에서 확인해보면 좋은 옵션 : Connect, Disconnected(연결 되어있는지 안되어있는지 확인할 수 있음), Receive Buffer, Send Buffer(소켓연결 성공->비어있음, 실패 -> 차있음), Callback(on했던 List들 정보 저장)
alecture, back socket.io version 2로 맞춰주지 않으면 통신에러가 발생한다
npm i socket.io@2
1. 아래 주황색화살표 : 서버에서 프론트로 데이터를 보내줌. SID(소켓 아이디 받아서 연결. 같이 알고있음)
2. 위로 초록색화살표 : 프론트에서 서버로 ws-sleact라는 name space에 연결한다는 요청 전달
3. 4.
5. 서버에서 데이터를 hello라고 보냄
6. 프론트가 로그인 정보를 서버에 보냄
7. 서버에서 온라인 리스트를 보냄 (로그인 사람 정보)
브라우저를 종류하면 자동으로 socket.io 연결이 끊김
DM 내용 표시하기
chat Data 받아오는 코드? 기억나는지?
const { data: chatData, mutate: mutateChat, revalidate, setSize } = useSWRInfinite<IDM[]>(
(index) => `/api/workspaces/${workspace}/dms/${id}/chats?perPage=20&page=${index + 1}`,
fetcher,
);
변수명 큰거에 지어놓고, SASS 문법(CSS 전처리기) 활용해서 변수명 작성 `npm i node-sass`
img 사용하는 경우 alt는 필수! 이미지가 로딩 안되는 상황에 alt의 텍스트가 뜸
커스텀 스크롤바와 dayjs
React-Custom-Scrollbars 라이브러리
`npm i react-custom-scrollbars --save` `npm i --save-dev @types/react-custom-scrollbars`(type설치)
autoHide : 움직일때 스크롤바 나타나고, 가만히 있으면 꺼지는 옵션
<Scrollbars autoHide ref = {scrollbarRef} onScrollFrame={onScroll}>
</Scrollbars>
//div 역할을 함
채팅 전송날짜시간 라이브러리 : dayjs(요즘 추세, 불변성, 가벼움),date-fns, moment(참조성)
npm i dayjs
<sapn> {dayjs(data.createdAt).format('h:mm A')} </span>
멘션 기능 만들기
멘션+자동완성 -> React Mentions 라이브러리
`npm i react-mentions` `npm i --save-dev @types/react-mention`(type설치)
<Mention
appendSpaceOnAdd
trigger="@"
data={memberData?.map((v) => ({ id: v.id, display: v.nickname })) || []}
renderSuggestion={renderSuggestion}
/>
appendSpaceOnAdd : 멘션할때 커서 한칸 띄워줌
renderSuggestion : 컴포넌트 넣어주면 됨. 추천 리스트 렌더링 하는 방법 설정
const renderSuggestion = useCallback(
(
suggestion: SuggestionDataItem,
search: string,
highlightedDisplay: React.ReactNode,
index: number,
focus: boolean,
): React.ReactNode => {
},
[memberData],
);
로딩중에 띄우지 않기, 로딩이 되었다면 EachMention 버튼 띄우기, 해당하는 사람 하이라이트(highlightedDispaly)
React.ReactNode => {
if (!memberData) return;
return (
<EachMention focus={focus}>
<img
src={gravatar.url(memberData[index].email, { s: '20px', d: 'retro' })}
alt={memberData[index].nickname}
/>
<span>{highlightedDisplay}</span>
</EachMention>
);
},
Emotion css에서는 변수를 쓸 수 있다. focus == true? 렌더링, false? 렌더링무시. js문법 사용
${( {focus} ) =>
focus &&
background:#1264a3;
color : white;
};
함수를 호출하는 방법
function a();
a();
a.call();
a.apply();
a.bind()();
a``; //tagged template literal
//위의코드, 이런형식으로 함수 불러옴
a`${ () => {} }`;
Mentions 함수 css styled 적용
export const MentionsTextarea = styled(MentionsInput)`
@(nickname)(idnumber)
느낀점?
실시간 채팅을 위한 소켓통신할때 눈물나는 줄 알았는데.. 다른사람 채팅 받아오는 socket.io 연결이 한번 더 남았다고 한다.. 그때는 잘 할 수 있겠지?
간단히 hooks 를 만들고 서버와 연결하면 된다고 말 할 수 있지만 코드로 짜면서 구성을 보면 꽤 복잡하고 어려웠다. 그래도 결국 띄웠으니! 혼자서도 할 수 있도록 연습하고싶다.