[React-Native] @tanstack/react-query v4 기능

DaYoung·2024년 5월 14일

React-Native

목록 보기
29/35

React-Query 사용하는 이유?

axios는 HTTP 요청을 보내고 응답을 받는 데에 중점을 두는 반면,
React Query는 데이터의 상태 관리와 최적화에 초점이 있다.

1.caching, fetching, 동기화, 서버 업데이트, 에러 핸들링 등 서버 상태를 업데이트 하는데 좀 더 쉽게 만들어준다.

  1. side effect 제거
    상태마다 작성해줘야 하는 useEffect를 제거할 수 있다.
    useEffect를 사용하여 최초 렌더링 했을 때 api 호출 한 함수를 불러오게끔 작업을 해줘야하는데 react query가 내장된 기능으로 관련된 로직을 처리해주니 획일화 된 방식으로 코드를 작성할 수 있어서 좋다.

3.caching을 통해 반복적인 비동기 데이터 호출을 방지하여, 애플리케이션 속도를 향상 시킨다.

4.오래된 데이터의 상태를 파악하여 updating을 지원한다.

5.프로젝트가 기존보다 단순해져 새로운 기능을 만들기 쉬워졌고, 유지보수 하기에도 간편해졌다.

6.복잡하고 장황한 코드가 필요한 다른 데이터 불러오기 방식과 달리 React Component 내부에서 간단하고 직관적으로 API를 사용할 수 있다.

그 외에도 여러가지 장점이 있다.
따라서 axios와 React Query를 함께 사용하면 HTTP 요청과 데이터와 상태 관리를 효과적으로 조합하여 구현할 수 있다!


그 외에 react query를 사용하는 이유
https://velog.io/@carrotdy/React-Native-React-Query



React-Query v3 vs v4 비교

1.install

v4부터 React Query에서 @tanstack/react-query로 패키지가 변경되었다.

v3

yarn add react-query

v4

yarn add @tanstack/react-query
yarn add @tanstack/react-query-devtools

2.devtools

  • v4에서는 @tanstack/react-query-devtools 별도의 패키지 설치가 필요하다.
  • 웹에서 가능
  • devtools를 사용하면 React Query의 모든 내부 동작을 시각화하는 데 도움이 되며(어떻게 작동하는지 쉽게 확인 가능), 문제가 발생하면 디버깅 시간을 절약할 수 있다.
    예를 들어, 어떤 데이터가 로딩 중인지, 어떤 쿼리가 에러를 발생시키는지 등을 쉽게 볼 수 있다. 이는 문제가 발생했을 때 디버깅 시간을 절약하는 데 도움이 된다.

v3

return (
  <QueryClientProvider client={queryClient}>
    <AppMain />
  </QueryClientProvider>;
)

v4

import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const App = () => {
  const queryClient = new QueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools initialIsOpen={false} />
      <AppMain />
    </QueryClientProvider>
  );
}

3.queryKey

v3에서는 queryKey를 문자열 또는 배열로 지정할 수 있었다.
사실 React Query는 내부적으로 항상 Array Keys로만 작동했기 때문에
v4에서는 배열로 통일 시켰다.

v3

- useQuery("todos", fetchTodos);

v4

+ useQuery(["todos"], fetchTodos);

4.status idle 상태 제거

쿼리의 상태를 더 세분화하여 관리하고 이를 기반으로 애플리케이션의 UI나 로직을 더 정확하게 제어할 수 있도록 한다.
따라서 쿼리의 로딩 상태를 더 정확하게 파악하고 처리할 수 있게 된다.

v3

- status: "idle";
  • status: "idle": 쿼리가 비활성화된 상태임을 나타낸다.
    이는 쿼리가 아무 작업도 수행하고 있지 않고, 로드되지 않았거나 실행되지 않았음을 의미한다.

v4

+ status: "loading";
+ fetchStatus: "idle";  // fetchStatus: 쿼리의 데이터 페치 상태
  • status: "loading": 쿼리가 현재 데이터를 로드 중이거나 요청을 보내는 중임을 나타낸다. 이는 이전의 idle 상태보다 더 구체적으로 데이터가 로딩 중임을 나타낸다.
  • fetchStatus: "idle"
    fetchStatus === 'fetching': 쿼리가 현재 실행 중이며 데이터를 요청 중임을 나타낸다.
    fetchStatus === 'paused': 쿼리를 요청했지만, 일시적으로 중단된 상태를 나타낸다.
    fetchStatus === 'idle': 쿼리가 아무 작업도 수행하고 있지 않고, 데이터를 요청하지 않은 상태를 나타낸다.



<참고>
https://github.com/ssi02014/react-query-tutorial/blob/main/document/v4.md



대표적인 기능들


🔍 useQuery

  • 데이터 조회 및 캐싱의 역할
  • queryKey와 queryFn을 필수로 선언
  • queryKey는 배열로 지정해 줘야 한다.
    - 이는 단일 문자열만 포함된 배열이 될 수도 있고, 여러 문자열과 중첩된 객체로 구성된 복잡한 형태일 수도 있다.
   // An individual todo
useQuery({ queryKey: [ "todo", 5 ], ... })

// An individual todo in a "preview" format
useQuery({ queryKey: [ "todo", 5, { preview: true } ], ...})
  • 옵션
    • select: 쿼리 함수에서 반환된 데이터를 변경하거나 선택할 수 있음
    • enabled: 특정 조건이 만족되었을 때, 쿼리 활성화
    • retry: 쿼리를 재시도하는 횟수나 조건을 설정함
    • onSuccess: 쿼리가 성공적으로 데이터를 가져왔을 때 실행 할 콜백 함수
    • onError: 쿼리가 실패했을 때 실행할 콜백 함수 (오류 처리에 사용)
    • onSettled: 쿼리가 완료되었을 때(성공 또는 실패 모두 포함) 실행할 콜백 함수
    • refetchOnWindowFocus: 페이지나 탭이 다시 포커스를 받을 때 쿼리를 자동으로 다시 불러올지 여부를 나타내는 부울 값
import { useQuery } from "@tanstack/react-query";

const QuerySelect = () => {
  const { data } = useQuery({
    queryKey: ["project"],
    queryFn: fetchProject,  //api 함수
    
    onError: async () => {  // 성공했을 때 실행
        navigate('BottomTab', {
            screen: 'Home'
        });
    },
      
    onSuccess: async () => {  // 성공했을 때 실행
        navigate('BottomTab', {
            screen: 'Home'
        });
    },
          
    select: (value: any) => value.filter((x: any) => x % 2 === 0)
    //위의 코드는 쿼리 함수는 1부터 6까지 숫자의 배열을 반환하지만, select 옵션을 사용하여 짝수 배열 값만 얻을 수 있다.
      
    enabled: isEnable
    //isEnable이 true 일 때만 쿼리 활성화
    
    retry: 3, // 최대 3번 재시도
    
    retryDelay: 1000, // 재시도 간격: 1초
  });

  return <div>{data?.join(",")}</div>;
};

export default QuerySelect;

🔍 useQueries

여러개의 데이터 쿼리를 가져올 때 유용하다.
각 쿼리는 별도의 요청으로 처리되므로 서버 응답 시간에 따라 시간이 오래 걸릴 수 있다.
하지만 useQueries는 캐싱 기능도 제공하기 때문에 동일한 쿼리가 여러 번 호출될 경우 이전에 캐싱된 결과를 반환하여 네트워크 트래픽을 절약할 수 있고, queryResults 배열을 활용하여 전체 쿼리에 대한 상태를 한 번에 처리할 수도 개별적으로 처리할 수도 있다.

ex) 웰잇고, Home에서 useQueries 훅을 사용하여 여러 개의 쿼리(디바이스 정보, 건강 등급 정보, 비즈 사이트 가져오기 등...)를 동시에 호출하고 있다.

  • 각 쿼리는 별도의 비동기 요청으로 처리되므로, 각각의 쿼리가 서로 영향을 주지 않고 독립적으로 실행이 된다.
  • 데이터 요청은 로딩 중인지(isLoading), 에러가 발생했는지(error), 성공적으로 데이터를 받아왔는지(data) 등의 상태를 가진다.
    useQueries는 이러한 각 요청의 상태를 효과적으로 관리할 수 있다.
  • React Query는 내부적으로 데이터를 캐싱하여 동일한 요청이 반복되어도 네트워크 요청을 줄일 수 있다.

useQuery

  const usersQuery = useQuery("users", fetchUsers);
  const teamsQuery = useQuery("teams", fetchTeams);
  const projectsQuery = useQuery("projects", fetchProjects);

useQueries
배열을 활용하여 전체 쿼리에 대한 상태를 한 번에 처리할 수도 개별적으로 처리할 수도 있다.

const { data, isLoading } = useQueries({
    queries: [
      { queryKey: ['deviceInfo'], queryFn: fetchDeviceInfo},
      { queryKey: ['healthGrade'], queryFn: fetchHealthGrade },
      { queryKey: ['bizSite'], queryFn: fetchBizSite },
    ],
})
const [
    { isLoading: isLoadingDeviceInfo, data: dataDeviceInfo },
    { isLoading: isLoadingHealthGrade, data: dataHealthGrade },
    { isLoading: isLoadingBizSite, data: dataBizSite }
  ] = useQueries([
    { queryKey: 'deviceInfo', queryFn: fetchDeviceInfo },
    { queryKey: 'healthGrade', queryFn: fetchHealthGrade },
    { queryKey: 'bizSite', queryFn: fetchBizSite },
]);

🔥 staleTime VS cacheTime

React에서 cacheTime과 staleTime은 React Query라는 라이브러리에서 사용되는 옵션이다.
이 옵션들은 데이터의 캐싱 및 유효성 검사에 관련된 기능을 제어하는 데 사용된다.


👉🏻 staleTime

  • 캐시된 data가 신선한 상태(fresh)로 남아있는 시간
  • 특정 data에 대해 설정해준 stale time이 지나게되면, 그 data는 신선하지 않은 상태(stale)로 간주된다.
  • staleTime은 밀리초 단위로 설정되며, 기본값은 0
  • staleTime이 0보다 큰 값으로 설정되면, staleTime 이후에도 이전 캐시 결과를 사용할 수 있다.
    이렇게 하면 네트워크 요청을 최소화하고, 사용자 경험을 개선할 수 있다.

👉🏻 cacheTime

  • cacheTime은 쿼리 결과를 캐시로 저장하는 기간을 지정한다.
  • cacheTime은 밀리초 단위로 설정되며, 기본값은 5분(300000 밀리초)
  • cacheTime이 경과하면 React Query는 다시 쿼리를 수행하여 최신 데이터를 가져온다.

글로 봐서는 정확하게 이해할 수가 없었다ㅠㅠ
stale과 cache는 비슷한 역할을 하는 것 같은데.. 왜 stale과 cache를 나눠서 관리할까?

위 그래프에서 페이지 이동 직전에 캐시가 만료되었다고 가정해보겠다!
두개의 차이점은 C이다.
현재처럼 stale과 cache로 관리할 경우,
C에서 stale 상태의 캐싱된 데이터를 이용하여 UI를 보여준 후
refetching을 완료하면 새로운 데이터를 이용한 UI로 교체해줄 수 있다.

반면 cache만으로 관리하게 된다면,
캐시가 만료되어 삭제되었을테니 C에서 refetching이 완료되기 전까지 데이터가 없으므로 Loading 페이지를 보여줘야 한다.
그리고 refetching이 완료되서야 d 데이터를 보여줄 수 있다.
이렇게 되면 사용자 페이지는 이동 후 느리게 서비스를 이용하게 된다.


<참고>
https://growing-jiwoo.tistory.com/103 https://www.timegambit.com/blog/digging/react-query/03

🔍 useInfiniteQuery

  • 무한스크롤
  • 사용자가 스크롤 할 때마다 새로운 데이터를 가져온다.
import { useInfiniteQuery } from '@tanstack/react-query'

const { data, fetchNextPage, isFetchingNextPage, hasNextPage } =
    useInfiniteQuery(
      [QueryKey.RECORD_LIST],
      ({ pageParam = 1 }) =>
        RecordService.Record.list({ page: pageParam, size: 5 }),
      {
        getNextPageParam: (lastPage, allPages) => {
          const totalCount = lastPage.data.data.totalCount;
          const currentPageDataCount = lastPage.data.data.diaries.length;

          if (currentPageDataCount < totalCount) {
            return allPages.length + 1;
          } else {
            return undefined;
          }
        },
      }
    );

위 코드는 페이지네이션을 처리하고,
현재 페이지에서 반환된 데이터 수가 전체 데이터 수보다 작으면 (더 가져올 데이터가 남아 있으면) 다음 페이지를 가져오는 로직으로 구현하였다.


🔍 useMutation

  • 서버에 데이터를 업데이트 하도록 서버에 네트워크 호출을 실시함
  • 데이터 생성, 수정, 삭제 역할
import { useMutation, useQueryClient } from "@tanstack/react-query";


const queryClient = useQueryClient();
  
const { mutate } = useMutation(
    (diaryId: number) => DiaryService.diary.delete(diaryId),
    {
      onSuccess: async data => {
        if (data && data.status === 200) {
          Toast.show({
            type: 'success',
            text1: '삭제되었습니다.',
          });

          setIsVisibleMore(false);
          await queryClient.invalidateQueries([QueryKey.DIARY_LIST]); 
          //쿼리를 무효화함
        }
      },
      onError: (error) => {
        console.error('Delete error:', error);
      }
    }
  )
  
  
  ...
  
   <Pressable
      onPress={() => {
         if (deleteItemId !== null) {
            mutate(deleteItemId);
         }
      }}
      style={styles.ButtonContainer}>
         <Title
           text={"삭제"}
           fontSize={16}
           color={Colors.White}
           style={{ textAlign: "center" }}
         />
    </Pressable>
  • 삭제 버튼을 눌렀을 때, mutate(deleteItemId)를 호출한다.
  • DiaryService.diary.delete(diaryId) 선택한 항목을 삭제하는 API 호출을 한다.
  • 삭제가 성공하면,
    모달창이 false가 되어 닫히고 queryClient.invalidateQueries([QueryKey.DIARY_LIST])를 호출하여 일지 목록 쿼리를 무효화하여 최신 데이터로 갱신한다.

<참고>
https://tanstack.com/query/v4/docs/framework/react/reference/useQuery




<나중에 할 일>

1.v4로 변경
2.queryKey 관리
key를 통해서 데이터를 캐싱하고 관리하기 때문에 중요하다!
별도로 query-key를 한 곳에서 관리하는게 좋다

profile
안녕하세요. 프론트앤드 개발자 홍다영입니다.

0개의 댓글