실시간 반영 구현기

윤뿔소·2023년 2월 7일
0

팀 프로젝트: 맛피

목록 보기
6/8

CRUD 기능 실행 중 데이터를 보내 수정, 삭제, 생성을 했을 때 바로 적용되지않는 점 발생
변수가 변할 때(useEffect) get 요청을 보내 각각 변수가 변함을 알고 변할 수 있게 코드를 작성

실시간 반영

리액트는 SPA 개발에 맞춰진 JS 라이브러리다. SPA의 큰 특징 중 하나라 함은 Blinking, 즉 새로고침이 없다는 점이 가장 두드러지게 난다.

하지만 단점이 많은데 낮은 SEO, 사용자 부담 증가 등이 있지만 새로고침이 없어서 서버에 CREATE, UPDATE, DELETE 작업을 했을 때 변해진 값으로 바로 변하지 않아 새로고침을 해야 값이 반영되는 문제가 있었다.

이러한 문제를 해결하기 위한 사투를 기록하겠다.

원인

useAxios기록을 보면 알겠지만 우리는 서버와 비동기 통신을 할 때를 우리가 만들어서 했다. 커스텀훅을 이용해 비동기 통신 관리 관련 훅을 만들었다.

여기에는 실시간 반영 기능이 없기에 그 변수값이 바뀌어도 서버에서 그 데이터를 가져온 변수를 다시 GET하는 게 없다는 뜻이다.

그래서 해결하도록 해봤다.

해결법

프론트엔드 팀원들과 모여 논의를 했고, 각자 맡은 기능들이 달라(맛포스트는 모달 위에 렌더링, 맛픽커 리스트 및 맛플레이스 픽 수정 관련 등) 반영을 해야하는 기능들을 따로 만들어서 보고하는 식으로 했다.

나는 카카오맵 관련 기능 / 맛플레이스 검색 기능 / 마이페이지 / 무한스크롤 등을 기능 개발 중이었는데, 마이페이지에 있는 EDIT 기능을 제외하고 CRUD 작업을 적용할 게 없어서 마이페이지에 적용해야하는 실시간 반영을 기술하겠다.

보다싶이 마이페이지에 닉네임, 메모, 사진을 바꾸는 EDIT 기능이 있다. 저걸 바꾸려고 PATCH 요청을 보내면 바로 실시간 반영이 안되고 새로고침을 해야했다.

const MyPage: React.FC = () => {
  ...

  const [isChange, setIsChange] = useState<boolean>(false);
  ...
  const { axiosData: getMemberAxios, responseData: memberData } = useAxios(
    getMyData,
    [isChange]
  );
  
  // 유저정보 중략

  const [revisedName, setRevisedName] = useState(nickname);
  const [revisedMemo, setRevisedMemo] = useState(memo);
  const [revisedImage, setRevisedImage] = useState(profileUrl);

  // 프로필 이미지 수정을 위한 ref 중략...

  useEffect(() => {
    getMemberAxios();
  }, [isChange, isLoggedIn]);
  
  // 모달 관련, 이미지 관련 코드 중략...

  const onRevise = () => {
    updateAxios();
    onClickToggleEditModal();
    setIsChange(!isChange);
  };

  return (
    <FeedContainer>
      <div className="userInfo_header_container">
        <ImgContainer>
          {profileUrl ? (
            <UserImg src={profileUrl} alt="프로필사진" />
          ) : (
            <EmptyImg />
          )}
        </ImgContainer>
        <UserInfo>
          <UserNickname>{nickname}</UserNickname>
          {memo && <UserRemainder>{memo}</UserRemainder>}
          <UserRemainder>
            <FollowButton onClick={onClickToggleFollowingModal}>
              팔로잉 {followings}
            </FollowButton>
            <FollowButton onClick={onClickToggleFollowerModal}>
              팔로워 {followers}
            </FollowButton>
          </UserRemainder>
        </UserInfo>
        <EditIconStyled onClick={onClickToggleEditModal} />
        <LogoutIconStyled onClick={onClickToggleLogoutModal} />
      </div>
      <ContentContainer>
        <TabContainer>
          <div className="tab_menu focused" aria-hidden="true">
            Post
          </div>
          <div className="tab_menu" onClick={onClickTab} aria-hidden="true">
            Pick
          </div>
        </TabContainer>
      </ContentContainer>
      <MyPagePostInfo />
      
    // 모달 관련 코드 중략...

      {isOpenEditModal && (
        <ModalPortal>
          <ModalContainer>
            <ModalView>
              <Header>정보 수정하기</Header>
              <div>
                <EditUserImg
                  src={revisedImage || profileUrl}
                  alt="프로필 사진"
                  onClick={onClickImg}
                />
                <input
                  type="file"
                  accept="image/jpg,impge/png,image/jpeg"
                  name="profile_img"
                  className="image_upload"
                  onChange={onChangeImage}
                  ref={fileInput}
                />
              </div>
              <Input
                type="text"
                value={revisedName || ""}
                onChange={onChangeName}
              ></Input>
              <Input
                type="text"
                value={revisedMemo || ""}
                onChange={onChangeMemo}
              ></Input>
              <div className="button_container">
                <ModalBtn onClick={onRevise}>제출</ModalBtn>
                <ModalBtn onClick={onClickToggleEditModal}>취소</ModalBtn>
              </div>
            </ModalView>
          </ModalContainer>
          <ModalBackdrop onClick={onClickToggleEditModal} />
        </ModalPortal>
      )}
  ...

결론부터 얘기하자면 useState를 사용해 isChange라는 상태를 만들었고, 이 상태가 true면(변한다면) useEffect를 이용해 GET 작업을 실행하도록 변경했다.

해결 중 문제

처음 문제를 해결할 때 isChange가 없는 상태로 진행했었다. 이때 내가 어떤 생각이었냐면

useEffect의 의존성 배열에 변수를 집어넣으면 변수가 변하니 revise 관련 상태의 변수를 집어넣으면 되겠지

이 생각이었다. 하지만 저걸 넣으려니 당연히 안됐다. revised 관련 변수가 변하는 걸 어떻게 변하는지와 set을 이용해 입력할 때 변수가 변하게 한다면? 이랬는데 변할 때마다 onChange를 이용해 GET해온다면 서버 통신이 쓸데없이 많아져 골머리를 앓았다.

그래서 구글링을 통해 boolean 데이터 관련 변수를 만들어줘 그 걸 기준으로 하면 어떨까 생각을 하게 됐고, isChange라는 변수를 만들어 사용했다.

쓰다보니 isChange가 되게 중요한 존재였다. useEffect 뿐만 아니라 useAxios에게도 isChange가 변한다면 너도 변해라 라는 단서를 주니 useAxios한테도 되게 중요한 데이터였다. 또한 변해야하는 변수(revised 3대장)들이 많은데 isChange로 실시간 반영이 일어나야하는 부위에 넣어주기만 하니 편하게 할 수 있었다. 또 if문을 활용하여 할 수도 있으니 활용성도 좋았다.

결론

아직도 나는 멀었다. 간단한 서버 통신과 실시간 반영은 쉽게 했었는데 관리해야할 데이터들(맛포스트, 맛플레이스, 마이페이지 등)이 많아지면 그렇게 하면 안된다고 깨달았다.

또한 useEffect의 존재에 더욱 감사하며 동작원리에 대해서 제대로 알아야겠다고 생각했다. 너무 효율적이고도 좋은 존재다.

그리고.. 단골 손님인.. React-Query는 맨날 나온다.. 실시간 반영 문제를 해결하고 프로젝트도 끝난 뒤 이 포스트를 작성하며 또 찾아봤는데 이러한 문제를 해결하는 방안을 더 찾아봤다.

  1. useEffectuseState를 이용해 GET
  2. 리액트 전용 라이브러리인 react-hot-loader를 사용
  3. React-Query의 refetch
  4. (리덕스 사용 시) 리덕스 thunk, saga

이런 식으로 나눠졌다. 역시 바퀴를 다시 발명하지 마라 했던가.. 글고 리액트 쿼리는 무슨 만능인가?? 맨날 나와 무슨;;
쨌든 결론은 리액트 쿼리에 대해서 무조건!! 알아야한다. option까지 붙이는 것도 생각해야하고! 리액트 쿼리는 진짜 재밌고 유익하게 배울 수 있을 거 같다.

뭔가 더 나은 해결점이 많았지만 그래도 배운 점이 많아서 되게 유익했다. 실패하고 공부하고 성장하고 나아가자! 화이팅

profile
코뿔소처럼 저돌적으로

5개의 댓글

comment-user-thumbnail
2023년 2월 25일

프로젝트하면서 돌아만가면 거기에서 끝낸던 제가 부끄럽게 느껴지네요 ㅎㅎ 고찰하는 모습 멋있습니다!

답글 달기
comment-user-thumbnail
2023년 2월 25일

프로젝트 기능 하나하나 정말 꼼꼼히 하셨네요 ㅠ 존경합니다

답글 달기
comment-user-thumbnail
2023년 2월 26일

리팩토링 생각만 ,, 하고 있었는데 진짜 대단하신 것 같습니다 !!

답글 달기
comment-user-thumbnail
2023년 2월 26일

정석대로 가시는군요. 좋은 라이브러리가 있어도 왜 이게 필요해서 만들어졌는지를 모르면 반쪽짜리 공부가 되겠죠. 그런면에서 뿔소님의 이런 공부방법이 나중에는 기억에 더 오래 남을 거 같습니다!

답글 달기
comment-user-thumbnail
2023년 2월 26일

프로젝트를 정말 정성들여 구현하신게 느껴져요 ㅎㅎ 잘 보고 갑니다!

답글 달기