ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ 리액트 ] 데이터를 효과적으로 관리하는 방법에 대하여 - 리액트 쿼리와 커스텀 훅
    web 2023. 7. 21. 00:00

    어떠한 서비스를 만들어도, 클라이언트와 서버의 구분없이 활발한 것이 바로 '데이터 관리'라고 생각한다. 잘하는 클라이언트가 되려면 구조를 잘 짜는 클라이언트가 되어야한다고 생각하는데, 결국 구조를 잘 짜려면 '데이터를 어떻게 가져와야하는지'에 대한 고민이 필연적이다. 

    이번 프로젝트에서는 서버로부터 가져와야할 데이터가 정말 많았고, 그래서 서버 데이터를 어떻게 가져오고 가공할지에 대한 고민이 많았다. 결론적으로는 (1) 리액트쿼리를 이용, (2) 커스텀 훅으로 패칭하는 방식으로 구현을 했다. 

    그 이유는, 서버에서는 한 페이지에 해당하는 데이터를 한 번에 보내주는데, 클라는 컴포넌트를 최대한 쪼개야했기 때문이다. 서버가 보내주는 데이터를 있는 그대로 사용할 수 있는데, 굳이 setState로 담아 데이터를 두 번 관리할 필요는 없다고 생각했기 때문에, 있는 그대로의 데이터를 효과적으로 사용하는 목적성을 가진 react-query 기술을 채택했다. 뿐만 아니라, 패칭해온 데이터를 어떻게 컴포넌트에 뿌려줄지에 대한 고민도 많았는데, props나 atom보다는 리액트쿼리로 api통신하는 과정을 커스텀 훅으로 만드는 방법이 가장 좋아보였다. 그 이유는 첫째, 한 페이지에 보여지는 데이터가 하나의 api로 오는 경우가 많은데 컴포넌트를 잘게 쪼갠 상태에서 모두 props를 내려주게 되면, drilling이 너무 심해지기 때문이다. 둘째, 마찬가지로, 한 페이지에 보여지는 데이터가 하나의 api로 오는 경우가 많은데, 그 페이지에서만 관리될 데이터를 전역변수로 선언하는 것은 어색하다고 생각했기 때문이다. 전역적으로 쓰이는 게 아니라, 쪼개진 컴포넌트에서 공통적으로 사용될 데이터인데 전역변수로 선언하는 것은 불필요하다고 생각했다. 

     

     

    여기서 잠깐! 내가 전역적으로 쓰이지 않는 경우에 전역변수 선언하는 것을 꺼려한 이유는 다음과 같다. 

    1. 변수의 생명주기 : 전역변수는 함수 안의 변수와 다르게 함수가 return될 때 생명주기가 끝나지 않고, 어플리케이션이 끝날 때까지 죽지 않는다 → 변수의 긴 생명주기는 어플리케이션의 성능저하로 이어진다고 한다
    2. 암묵적 결합 : 언제 어디서든 전역변수를 참조하고 변경할 수 있는데, 의도치 않은 상태의 변경이 일어날 수 있다
      https://intzzzero.netlify.app/blog/no-more-global-variables
       

    다음과 같은 이유로 우리 팀은 아래와 같이 전역변수를 지양하면서 적절히 이용하기로 컨벤션을 정하였다. 

    🔥 전역변수의 사용 ⇒ 쓰게되면 근거+알려주기

    1. 해당 변수가 페이지가 다른 컴포넌트에서 공통적으로 쓰일 때. (token 등)
    2. 해당 변수가 부모 위계의 컴포넌트에서 쓰여야 하는데, 변수가 필요한 컴포넌트들의 “공통 부모”로 가는 depth가 2 이상일 때. ( 그저 프롭스를 전달하는 용도로 프롭스가 내려받아지는 컴포넌트가 생길 때) → 프롭스 드릴링

    🔥 지역변수의 사용

    1. 위에 해당하지 않는 모든 경우
    2. 그냥 부모 컴포넌트에서 자식 2개에 프롭스 내려주면 되는 경우.

     

    다시 데이터 관리에 대한 이유로 돌아오면, 결론적으로 데이터를 get해와서 여러 컴포넌트의 뿌리는 경우, 리액트쿼리로 데이터 패칭을 하고 커스텀 훅으로 데이터를 return해주는 방식을 이용했다. 리액트 쿼리는 매번 공부해도 새로운 것 같다. 

     

    리액트쿼리란 무엇인가?

    리액트쿼리란 데이터 Fetching, 캐싱, 동기화, 서버 쪽 데이터 업데이트 등을 쉽게 만들어 주는 React 라이브러리

     

    리액트쿼리를 사용하는 이유

    • 데이터뿐만 아니라 isIdle, isLoading, isFetching, isSuccess, isError등과 같은 여러 가지 부가적인 상태 값들이 제공된다.
    • query key를 통하여 데이터 캐싱과 각 쿼리 간에 디펜던시 조작도 손쉽게 할 수 있다.
    • useEffect로 처리했던 여러 가지 상황들을 refetchOnMount, refetchOnReconnect, refetchOnWindowFocus와 같은 옵션으로 쉽게 처리할 수 있다.
    • 데이터 캐싱
    • get을 한 데이터에 대해 update를 하면 자동으로 get을 다시 수행한다. 
    • 데이터가 오래 되었다고 판단되면 다시 get해준다. (invalidateQueries)
    • 동일 데이터 여러번 요청하면 한번만 요청한다. (중복 호출 허용 시간 조절이 가능하다. )
    • 무한 스크롤

     

    데이터를 get해오는 최종코드는 아래와 같다.

    getPaymentRecordView.ts

    import axios from "axios";
    
    export async function getPaymentRecordView(paymentRecordIdx: number) {
      const data = await axios.get(`${import.meta.env.VITE_APP_BASE_URL}/api/payment-record/${paymentRecordIdx}`, {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${import.meta.env.VITE_APP_TEACHER_TOCKEN}`,
        },
      });
    
      return data.data.data;
    }

    useGetPaymentRecordView.ts

    import { useQuery } from "react-query";
    import { getPaymentRecordView } from "../api/getPaymentRecordView";
    
    export default function useGetPaymentRecordView(paymentRecordIdx: number) {
      const { data: paymentRecordView } = useQuery(["getPaymentRecordView"], () => getPaymentRecordView(paymentRecordIdx), {
        onError: (error) => {
          console.log(error);
        },
        staleTime: 3000,
      });
    
      const lesson = paymentRecordView?.lesson;
      const paymentDate = paymentRecordView?.paymentDate;
      const cycle = lesson?.cycle;
      const endDate = cycle?.endDate;
      const startDate = cycle?.startDate;
      const value = cycle?.value;
      const idx = lesson?.idx;
      const studentName = lesson?.studentName;
      const subject = lesson?.subject;
    
      return { lesson, paymentDate, cycle, endDate, startDate, value, idx, studentName, subject };
    }

     

    리액트 쿼리에는 정말 다양한 옵션이 있지만, 그 중에서도 staleTime을 3초 주었다. 리액트 쿼리는 기본적으로 staleTime은 0분, cacheTime은 5분으로 제공된다고 한다. 한 페이지에서 쪼개진 여러 컴포넌트에서 여러번 데이터 패칭을 하기 때문에, 서버와 계속해서 통신을 하는 것이 아니라 한 번 통신하면 저장된 캐시 데이터를 이용하는 것이 좋겠다고 생각해서 아주 짧게나마 staleTime을 주었다. 

     

    또한 우리 서버의 데이터는 뎁스가 깊었다. 그래서 패칭해온 데이터를 구조분해할당해서 사용했었는데, undefined가 굉장히 많이 일어나면서 애를 좀 먹었고, 결국은 커스텀 훅 안에서 옵셔널 체이닝으로 쪼개서 return해주었다. 리액트 쿼리 기술을 채택한 이유가 서버의 데이터를 있는 그대로 효과적으로 사용하기 위함이었기 때문에, 따로 onSuccess 옵션에서 setState를 담는 것은 그리 좋은 방식이라고 생각하지 않았기 때문에 useState는 사용하지 않았다. 

     

     

    댓글

Designed by Tistory.