[개발일지] 리팩토링 세 번째 - 새로운 채팅방

김선종·2021년 12월 2일
1

오늘한일

  • 메신저 어플리케이션에 새로운 채팅방 생성 기능 구현

또 기능을 넣어보았다

기존의 어플리케이션은 채팅방 데이터에서 읽어와서 채팅방이 없으면 그냥 에러를 띄워버렸다. 세상 무책임하네
친구 추가 기능도 업데이트 했겠다, 기존에 채팅방이 존재하지 않던 친구를 클릭하면 새로운 채팅방을 생성하는 기능을 만들어보기로 했다.

본론부터 들어가자

로직은 매우 간단하다. 기존에 채팅방에 입장하면 parameter로 받는 friendId를 통해 채팅방 목록에서 해당 채팅방을 찾아 리턴한다. (getCurrentChatroom()에서)

우선 chatroomContext의 리듀서에 새로운 채팅방을 추가하는 코드를 추가하였다.

// chatroomContext.tsx

const chatroomListReducer = (
  state: chatroomList,
  action: chatroomListAction
): chatroomList => {
  switch (action.type) {
    case 'chatrooms/updateMessage':
      const restChatrooms = state.filter(
        (chatroom) => chatroom.friendId !== action.data.friendId
      );
      return [action.data, ...restChatrooms];

    case 'chatrooms/createChatroom': // new chatroom
      return [...state, { friendId: action.data, chats: [] }];
    default:
      return state;
  }
};

그 다음 getCurrentChatroom() 에 다음과 같은 로직을 추가했다.

  • friendId를 통해 채팅방을 찾는다
  • 있으면 그대로 리턴한다
  • 없으면 chatroom을 하나 생성한 다음에 그걸 리턴한다.
// useChatroomContext.ts 

  const getCurrentChatroom = (friendId: number | string): chatroom => {
    const chatroom = chatroomListContext.find(
      (chatroom) => chatroom.friendId === parseInt(friendId as string)
    );

    if (!chatroom) {
      chatroomListDispatch({
        type: 'chatrooms/createChatroom',
        data: friendId as number,
      });
    }

    return chatroomListContext.find(
      (chatroom) => chatroom.friendId === parseInt(friendId as string)
    )!;
}

뭐 UI가 추가되는 것도 아니니 기능추가 그거 가볍네~ 라고 자신있게 말하며 테스트를 했으나,,,

같은 실수는 반복해서는 안된다

왜그런것이지... 하고 계속 생각을 해봤는데,,
항상 까먹는 문제가 있다, 리액트에서 상태변경 (setState, useState, useReducerdispatch 등...)은 항상 비동기로 동작 한다는 사실을...

상태를 변경하고 그 함수가 끝나기도 전에 바로 상태를 사용하려고 하니 당연히 못찾지... 그래서 방법을 고민하다가 결국 chatroomContext 에서 필터링을 해서 리턴하지 말고 첫번째에는 그냥 chatroom이랑 똑같이 생긴 객체만 리턴해도 괜찮지 않나? 하는 생각이 들었다.

// useChatroomContext.ts

const getCurrentChatroom = (friendId: number): chatroom => {
    const chatroom = chatroomListContext.find(
      (chatroom) => chatroom.friendId === friendId
    )!;

    if (!chatroom) {
      chatroomListDispatch({
        type: 'chatrooms/createChatroom',
        data: friendId as number,
      });

      return {
        friendId: friendId,
        chats: [],
      } as chatroom;
    }

    return chatroomListContext.find(
      (chatroom) => chatroom.friendId === friendId
    )!;
};

그러고 나니 아주 깔끔하게 채팅방이 들어가진다.

머스크형이랑 짧은 대화도 나눴다


나 자신도 결국 유저 목록에 있기 때문에 나와의 채팅방 또한 생성된다. 원래 헤더 프로필을 누르면 채팅 보내는 유저가 토글되는데, 이건 나와 나의 채팅이니까 눌러도 바뀌진 않는다. 얼떨결에 나와의 채팅까지 구현....^^

해결하지 못한 문제가 남았다

두가지의 문제가 있는데, 원인은 같은 것으로 예상이 된다만...

복제인간을 개발하였습니다...!

채팅방을 새롭게 생성하고 아무런 채팅도 하지 않고 나가게 되면, 채팅방이 두개가 생긴다...

머스크형이 두명,,, 트위터 헛소리도 두배...
물론 채팅방에 들어가서 채팅을 치면 다시 한개로 정상작동한다.

디버거를 통해 디버깅을 해보았더니,,, 컴포넌트 함수가 두번씩 실행되는 것이지 않는가... 홍진호도 아니고 왜 두번씩 실행되는거지 싶어 원인을 찾아보았다.

CRA로 프로젝트를 생성하고 index.tsx를 보면 <App><React.StrictMode>로 감싸져있는것을 볼 수있다. strict mode로 동작을 한다는 의미인데, 리액트는 development 환경 에서 strict mode로 동작시 예상못한 부작용을 찾는데 도움을 주기 위해서 아래의 것들을 두번씩 실행한다고 한다.

  • 클래스 컴포넌트의 constructor, render 그리고 shouldComponentUpdate 메서드
  • 클래스 컴포넌트의 getDerivedStateFromProps static 메서드
  • 함수 컴포넌트 바디
  • State updater 함수 (setState의 첫 번째 인자)
  • useState, useMemo 그리고 useReducer에 전달되는 함수

물론 production 환경에서는 한번씩만 실행한다.

자세한 내용은 공식문서 참고

나의 문제가 발생하는 이유는 getCurrentChatroom()이 순수함수가 아니기 때문이었다. friendId가 같더라도, 최초에 만들어지는지, 아니면 이미 존재하는지에 따라 chatroomContext를 선택적으로 업데이트한다. 따라서 이런 의도치 못한 동작이 발생하는 것 같다. 이는 더 좋은 방법을 고민해봐야 할 것 같다.

너가 거기서 그러면 안돼.

존재하지 않던 채팅방을 처음에 입장하게 되면 아래와 같은 경고가 발생한다.

Chatroom을 렌더하면서 ChatroomContextProvider 컴포넌트를 업데이트 하지 말라는 의미인데, 리액트 v16.13.0 부터 (참고) 이런 식으로 렌더중인 컴포넌트 외의 컴포넌트의 업데이트는 사이드 이펙트의 위험이 있기 때문에 경고를 띄우게 된다. 리액트 공식 깃허브에 올라온 이슈에 따르면 useEffect를 통해 업데이트 하라고 하는데, 아직은 어떻게 구조를 짜야할지 감이 잘 안온다. 이 부분도 공부해봐야 할 것 같다.

Restrospect

  • 단순 기능 구현만 하는 단계는 이제 벗어나자
  • side effect에 대해 고려해야 할 것 같다.
  • 상태 변경하는 함수는 항상 순수함수여야 함을 기억하자.
profile
개발자가 되고싶다 열심히하자

0개의 댓글