ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React Query 도입기
    Frameworks, Platforms and Libraries/React 2023. 11. 9. 23:07

    Store에서 서버 상태 관리(비동기 처리 상태 관리) 분리를 위해 React Query를 도입하게 되었습니다. 이전 포스팅을 통해 여러 서버 상태 관리 라이브러리를 비교했고 팀원들의 의견에 따라 React Query를 선택하게 되었습니다. React Query를 현재 프로젝트에 도입하여 사용한 내용을 정리해 보았습니다.

     

    기본 셋업

    React Query를 설치합니다. (React v16.8 이상 호환됩니다.)

    npm i react-query
    # or
    yarn add react-query

     

    React Query Provider를 main에 선언합니다.

    // main.tsx
    ...
    
    import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
    
    ...
    
    const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          refetchOnMount: false,
          refetchOnReconnect: false,
          refetchOnWindowFocus: false,
        },
      },  
    });
    
    ...
    
      ReactDOM.render(
        <Provider store={store}>
          <QueryClientProvider client={queryClient}>
            ...
          </QueryClientProvider>
        </Provider>,
        document.getElementById('root'),
      );
      
    ...

    불필요한 refetch를 막기 위해 Query Client 인스턴스화시 refetch를 막는 옵션을 추가합니다. 추가적으로 옵션이나 설정이 필요한 경우 추가해서 사용합니다.

     

    React Query 구성

    React Query Keys

    React Query에서는 각 Query 캐싱을 key로 관리합니다. key는 string 또는 Array 형태로 사용 가능하며 Array로 통일하여 사용하는 것을 추천합니다. key의 형식을 통일시키기 위함이며 string으로 사용 시에도 React Query 내부에선 Array로 변환합니다. Array key 사용 시에 동일 키로 인식하기 위해선 내부 요소의 order도 같아야 합니다.

    // Query Keys example
    // example 1 
    useQuery('key', ...) // queryKey === ['key']
    
    // example 2
    useQuery(['key1', 'key2', 'key3'], ...)
    // !== useQuery(['key1', 'key3', 'key2'], ...)
    // === useQuery(['key1', 'key2', 'key3'], ...)

     

    queries case

    Data를 GET 하는 케이스 예시입니다.

    // query.ts
    import { useQuery } from '@tanstack/react-query';
    import { Data } from '@/interface';
    import { getData } from '@/api';
    
    export const useGetData = () => {
      const context = useQuery<Data, Error>(['data'], async () => {
        const result = await getData();
        return result.data;
      });
    
      return context;
    };
    // Component.tsx
    import { useGetData } from './query';
    ...
    
    function Component() {
      const { isSuccess, isLoading, data } = useGetData();
    
      return (
        <Wrapper>
          {isLoading && <div>loading</div>}
          {isSuccess && <div>{data}</div>}
        </Wrapper>
      );
    }
    
    export default Component;

     

    특정 query의 state와 server status가 다른 컴포넌트에서도 공유가 필요한 경우 해당 query key를 가지는 queryClient.getQueryState()를 통해 가져올 수 있습니다. 

    // query.ts
    import { useQuery, useQueryClient } from '@tanstack/react-query';
    ...
    
    export const useDataContext = () => {
      return useQueryClient().getQueryState<Data>(['data'])!;
    };
    // ShareComponent.tsx
    import { useDataContext } from './query';
    ...
    
    function ShareComponent() {
      const { data } = useDataContext();
    
      return (
        <Wrapper>
          <div>{data}</div>
        </Wrapper>
      );
    }
    
    export default ShareComponent;

     

    paging처리 또는 infinite scroll처리가 필요한 경우엔 useInfiniteQuery를 사용합니다.

    // query.ts
    import {
      useQuery,
      useQueryClient,
      useInfiniteQuery,
    } from '@tanstack/react-query';
    import { getData, getPagingData } from '@/api';
    ...
    
    export const useGetPagingData = (pageSize: number) => {
      const context = useInfiniteQuery<Data, Error>(
        ['data', 'paging'],
        async ({ pageParam = 0 }) => {
          const result = await getPagingData({
            pageSize: pageSize,
            pageNo: pageParam,
          });
          return result.data;
        },
        {
          getNextPageParam: (lastPage) => {
            if (lastPage.hasNext) return lastPage.pageNo + 1;
            return undefined;
          },
        },
      );
      return context;
    };
    // PagingComponent.tsx
    import { useGetPagingData } from './query';
    ...
    
    function PagingComponent() {
      const { isLoading, isSuccess, data, hasNextPage, fetchNextPage, refetch } =
        useGetPagingData(10);
    
      useEffect(() => {
        refetch();
      }, []);
    
      // hasNextPage => 다음 page data 유무
      // fetchNextPage => 다음 page fetch
      ...
    }
    
    export default PagingComponent;

     

    mutations case

    Data의 Mutaion(POST, PUT, DELETE) 하는 케이스 예시입니다.

    // query.ts
    import {
      useQuery,
      useQueryClient,
      useInfiniteQuery,
      useMutation,
    } from '@tanstack/react-query';
    import {
      getData,
      getPagingData,
      editData,
    } from '@/api';
    ...
    
    export const useEditData = () => {
      const queryClient = useQueryClient();
    
      const context = useMutation(
        async (param: Data) => {
          await editData(param);
        },
        {
          onSuccess: () => {
            queryClient.invalidateQueries(['data']);
          },
        },
      );
    
      return context;
    };

    mutation의 경우 data의 무결성을 위해 최신화해줘야 합니다. mutation이 성공했을 경우 queryClient.invalidateQueries()을 이용하여 해당 key에 맞는 query를 다시 가져옵니다.

    // EditComponent.tsx
    import { useEditData } from './query';
    ...
    
    function EditComponent() {
      const editRestDataMutation = useEditRestData();
    
      // case mutate
      const editClick = () => {
        const param = {};
        editRestDataMutation.mutate(param);
      };
    
      // case mutateAsync
      const editAsyncClick = async () => {
        try {
          const param = {};
          await editRestDataMutation.mutateAsync(param);
        } catch (error) {
          console.log(error);
        }
      };
    
      ...
    }
    
    export default EditComponent;

    useMutation에서 데이터 후처리(Query invalidation)까지 했을 경우 mutate()를 사용하고 따로 처리가 필요한 케이스(redux) mutateAsync()를 사용합니다.

     

    마무리

    React Query의 도입으로 기존의 Store에서 비동기 처리(서버 데이터 처리)를 분리하여 Store의 코드가 많이 경량화되었고 캐싱된 데이터를 사용할 수 있으며 불필요한 refetch도 막을 수 있는 등 여러 이점을 얻을 수 있었습니다. Query의 구성을 몇 개 정도 예시로 보였지만  프로젝트의 각 상황에 맞게 Query를 구성하여 사용한다면 React Query를 좀 더 활용하여 사용할 수 있습니다. 클라이언트단에서 데이터 처리를 위해 서버 상태 관리 라이브러리의 도입이 점점 많아지고 있습니다. 필요에 따라 적절히 도입하여 사용한다면 많은 이점을 가져올 수 있겠습니다.

     


    참고 자료

    https://tanstack.com/query/v4/docs/quick-start

    https://tkdodo.eu/blog/practical-react-query

Designed by Tistory.