본문 바로가기

Project/나와 닮은 못난이 농산물 똑닮안

[나와 닮은 못난이 농산물 똑닮안] 4. 리팩토링1 (react-query, 로딩 컴포넌트)

반응형

개요

해커톤이 끝난 지 한 달이 조금 넘게 지났고, 그동안 꽤 많은 리팩토링이 진행되었다. 우선 지저분했던 코드들을 정리하며 폴더 구조 변경 및 로직을 개선했고, UX 향상을 위한 몇 가지 수정을 거쳤다. 이번 글에서는 리팩토링을 하며 직접적으로 와닿았던 4가지의 변화를 정리하고, 서비스의 기획/디자인 측면에서 대대적으로 변경된 결과 페이지를 소개하고자 한다.

 

1. React Query 도입

해커톤 당시 실패했던 react-query 도입. 너무 아쉬웠고 가장 빠르게 적용하고 싶었던 리팩토링 포인트였다.

 

근데 왜 도입하려고 했나?

1. react-query의 존재를 알게되었으니까! 프로젝트에 적용해보고 싶으니까! ( ?😮? )

2. API 호출 시 로딩, 에러를 깔끔하게 처리할 수 있으니까!

3. 캐싱을 통해 성능을 개선시킬 수 있으니까! (POST 요청에서는 의미가 없었다..ㅎ)

 

API 함수 변경

위와 같은 이유를 들며 일단 기존 API 함수를 변경했다. react-query에서 에러 처리를 할 것이기 때문에 기존의 try-catch가 필요 없어졌기 때문이다.

 

Before

export const getResult = async (data: SelectedProps) => {
  try {
    const res = await axios.post(`${baseURL}/crop/products`, data);
    return res.data;
  } catch (e) {
    console.log(e);
  }
};

After

export const getResult = async (data: SelectedProps) => {
  const res = await axios.post(`${baseURL}/crop/products`, data);
  return res.data;
};

 

API 호출 부분 변경

API를 호출하는 부분에 useMutation을 적용하면서 아래와 같이 변경되었다. 

  변경사항   Before   After
  isLoading, isError 관리   useState로 관리   useMutation 반환 값으로 관리
  호출 위치   결과 페이지로 이동할 때 호출   선택 페이지의 [결과 확인] 버튼 클릭 시 호출

 

Before (pages/result.tsx)

  const [isLoading, setIsLoading] = useState(true);
  const [isError, setIsError] = useState('');
  
  useEffect(() => {
    getResult(selected).then((res) => {
      if (res.result) {
        setResult(res.result);
        setResultType(res.result.type);
        setIsLoading(false);
      } else {
        setIsError(res.message);
        alert(res.message);
      }
    });

 

After (pages/select.tsx)

  const {
    data: resultData,
    mutate: resultMutation,
    isLoading: resultLoading,
    isSuccess: resultSuccess,
    isError: resultError,
  } = useMutation(getResult);

  const handleCaptureClick = () => {
    resultMutation(selectedState);
  };

  useEffect(() => {
    if (resultSuccess) {
      if (resultData.result) navigate('/result', { state: resultData.result });
      else {
        alert(resultData.message);
      }
    }
  }, [resultSuccess]);
  
  return (
    ...
  	<ImageFileUpload onClickButton={handleCaptureClick} />
    ...
  )

 

호출 위치 자체가 바뀌다 보니 결과 페이지로 이동 시 state로 API응답을 넘겨주어야 했지만, 그래서 뭔가 이게 맞는 건가? 싶은데 확실히 코드는 깔끔해졌다고 생각한다. 게다가 기존에는 결과 페이지에서 새로고침을 했을 때 다시 로딩이 뜨는 현상이 있었는데, 이 부분을 해결해서 너무 만족스러웠다.

 

Before (로딩 발생)

 

After (로딩 발생 X)

 

 

비하인드

사실 처음부터 위의 구조를 가져간 것이 아니였다. react-query를 잘 알지 못했고 (지금도 잘 모름..) useQuery 하나만 알고 있는 상태에서 냅다 적용한 것이기에, POST 요청에서는 useMutation을 쓰는 것이라는 것을 한참 찾아보다가 알게 되었다. 첫 시도에는 아래처럼 result 페이지의 useEffect에서 mutation을 호출했는데, 구글링 해봤을 때 이렇게 쓰는 사람은 없는 것 같아 지금 구조로 변경했다.

  const {
    mutate: resultMutation,
    isLoading: resultLoading,
  } = useMutation(getResult, {
    onSuccess: (data) => {
      if (data.result) {
        setResult(data.result);
        setResultType(data.result.type);
      } else {
        alert(data.message);
      }
    },
    onError: (error) => {
      navigate('/');
    },
  });

  useEffect(() => {
    resultMutation(selected);
  }, []);

 

 

2. 로딩 컴포넌트 개선

로딩 컴포넌트의 경우, 이미지들의 setInterval로 구현되어 있었다. 해커톤 당시 로딩 컴포넌트 구현 방법을 찾아봤을 때 이 방법이 나왔던 것 같다. 하지만, 이렇게 이미지를 활용할 경우 네트워크 탭에서 로딩이 이루어지는 동안 이미지들이 계속 반복적으로 다운로드 하는 현상을 발견했고(근데 사실 캐싱해서 성능에 영향은 없는 것 같기도..), 좀 더 찾아보니 GIF로 구현하는 것이 보다 일반적인 것 같았다.

 

그렇다고 한다

 

그래서 Image to GIF로 수정을 진행했고, 조금 더 나은 UX를 이루었다(라고 믿는다).

 

 

Before (Image setInterval)

 

After (GIF)

 

 

반응형