ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [라이브러리] React-query
    프로그래밍 2024. 2. 15. 15:57

    📋 개요

    • 비동기 상태 라이브러리를 채택하는 이유
    • Get Started
    • 페이징 처리 기법
    • Optimistic Update 구현

     

     


    ✅ 비동기 라이브러리를 채택하는 이유

     

    1️⃣  등장 배경

    • 기존의 상태 관리 라이브러리는 클라이언트 상태 작업에 적합하지만 서버 상태는 다르기에 비동기, 서버 상태 작업에 적합한 라이브러리가 필요해졌다.
    • 데이터를 비동기적으로 가져오고, 캐싱, 동기화, 업데이트 관리의 복잡성을 해결하고, 사용자 UI와의 상태를 동기화하는 과정을 보다 쉽고 효율적으로 하기 위해 등장하게 되었다.

     

     

    2️⃣ React-Query

    TanStack 팀에서 만든 라이브러리로 데이터 캐싱 및 기능적으로 더 많은 제어가 필요한 경우에 좋다.(기존 React 뿐만아니라 다양한 프레임워크에서 사용이 가능해 TanStack Query로 명칭이 변경되었다.

     

    📦 특징 및 장점

    • 서버 데이터를 관리하는 핵심 객체인 QueryClient에 대한 초기 설정이 필요하다.
    • Root 파일에서 QueryClientProvider 하위 컴포넌트를 감싸 적용할 수 있다.
    • 광범위한 쿼리 무효화 및 업데이터 옵션을 제공하여 데이터 업데이트 전략에 있어 더 많은 제어와 유연성을 제공한다.
    • 개발자 도구를 통해 현재 캐시된 쿼리, 쿼리 상태, 백그라운드 업데이트 등을 시각적으로 확인할 수 있다.

     

    📦 단점

    • 다소 복잡하고 많은 API의 개념으로 인해 러닝 커브가 상대적으로 높다.
    • 복잡한 캐싱 전걍이나 데이터 동기화를 사용하는 경우, 많은 설정과 구성을 필요로 할 수 있다.

     

     


    ✅ Get Started

     

    1️⃣ istall

    📦 NPM

    $ npm i @tanstack/react-query
    # or
    $ pnpm add @tanstack/react-query
    # or
    $ yarn add @tanstack/react-query

     

    📦 권장 사항 🔗

    $ npm i -D @tanstack/eslint-plugin-query
    # or
    $ pnpm add -D @tanstack/eslint-plugin-query
    # or
    $ yarn add -D @tanstack/eslint-plugin-query
    • 자체적으로 제공되는 eslint-plugin으로 일반적인 실수를 방지하는데 도움이 되며, 확장 기능을 추가해서 사용해야 한다.
    {
      "extends": ["plugin:@tanstack/eslint-plugin-query/recommended"]
    }

     

    2️⃣ Quick Start 

    📦 Query

    서버에서 데이터를 가져오기 위해 Promise 기반 메서드와 함께 사용할 수 있으며 고유 키에 비동기 데이터 소스를 연결하여 종속성을 가진다. 데이터를 수정하는 경우 Mutations를 사용하는 것이 권장된다.

     

    import { useQuery } from '@tanstack/react-query'
    
    function App() {
      const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
    }
    • 쿼리키, fetcher함수, Option 인자를 사용할 수 있다.
    • 이렇게 useQuery를 통해 생성된 개체에는 생산성을 높이는 몇가지 상태(status)가 포함되어 있으며, 그 외 쿼리 상태에 따른 더 많은 정보도 포함되어 있다.
    [Status]
    isPending:
    아직 데이터가 없다. (기존 isLoading)
    isError: 쿼리에 오류가 발생.
    isSuccess: 데이터를 사용할 수 있다.
    isFetching: fetcher함수가 실행될 때마다 true를 반환
     

    data: isSuccess를 통해 사용할 수 있는 데이터
    error: isError를 통해 알 수 있는 오류

     

    🎉 자주 쓰일 Option 🔗

    더보기

    enabled: 특정 조건을 만족했을 때 fetcher를 실행시키게끔 할 수 있다. (조건부 데이터 가져오기)

    initialData: 쿼리가 아직 생성되지 않았거나 캐시되지 않은 경우 초기값으로 사용된다.

    staleTime: 기본값이 0으로 데이터가 오래된 것을 간주된 후의 시간으로 infinity로 설정시 오래된 것으로 간주하지 않습니다.

    refethOnWindowFocus: 기본값이 true로 false로 설정하면 브라우저 창을 전환시에 데이터를 다시 가져오지 않는다.

     

    📦 Mutation

    일반적으로 데이터를 생성 / 업데이트 / 삭제 또는 서버 사이드 이펙트를 수행하는데 사용된다. 

     

    function App() {
      const {mutate, isPending, isError, isSuccess, error} = useMutation({
        mutationFn: (newTodo) => {
          return axios.post('/todos', newTodo)
        },
      })
    
      return (
        <div>
          {isPending ? (
            'Adding todo...'
          ) : (
            <>
              {isError ? (
                <div>An error occurred: {error.message}</div>
              ) : null}
    
              {isSuccess ? <div>Todo added!</div> : null}
    
              <button
                onClick={() => {
                  mutate({ id: new Date(), title: 'Do Laundry' })
                }}
              >
                Create Todo
              </button>
            </>
          )}
        </div>
      )
    }
    • 쿼리와 비슷하게 다양한 상태 및 정보를 가지고 있다. 

     

    📦 Invalidate

    특정 쿼리를 무효화하여 data를 stale상태로 만들어 data의 refecthing을 일으키게 하는 경우

     

    import { useState } from 'react';
    import { useQuery, useQueryClient } from 'react-query';
    import { updateProfile } from './api';
    
    const Profile = () => {
      const queryClient = useQueryClient();
      const [profile, setProfile] = useState({ nickName: 'jiyaho' });
      const userId = 'yourUserId'; // userId 값을 적절히 설정
    
      const handleEditProfile = async () => {
        // 프로필 업데이트 로직 (예: 서버 API 호출 또는 상태 업데이트)
        await updateProfile(profile);
    
        // 해당 유저의 데이터를 무효화(invalidate) 시키고 다시 가져오기(refetch)
        queryClient.invalidateQueries(`/user/${userId}`);
      };
    
      return (
        <div>
          {/* 프로필 수정 UI */}
          <input
            type="text"
            value={profile.nickName}
            onChange={(e) => setProfile({ nickName: e.target.value })}
          />
          <button onClick={handleEditProfile}>Edit Profile</button>
          
          {/* 여기에서 유저 정보를 출력하거나 UI 표시 가능 */}
        </div>
      );
    };
    
    export default Profile;

     

    📦 revalidate

    사용자가 캐시된 쿼리의 데이터에 대해서 업데이트를 하기 위한 목적으로 무효화전략이 아닌 재검증 전략을 통해 불필요한 네트워크 요청을 추가로 할 필요 없이 즉각 업데이트를 할 수 있다. 다만 서버에 직접 반영이 되는 것이 아닌 클라이언트 캐시에만 영향을 준다.

     

    import { useState } from 'react';
    import { useQuery, useQueryClient } from 'react-query';
    import { updateProfile } from './api';
    
    const Profile = () => {
      const queryClient = useQueryClient();
      const [profile, setProfile] = useState({ nickName: 'jiyaho' });
      const userId = 'yourUserId'; // userId 값을 적절히 설정
    
      const handleEditProfile = async () => {
        // 프로필 업데이트 로직 (예: 서버 API 호출 또는 상태 업데이트)
        await updateProfile(profile);
    
        // 해당 유저의 프로필 업데이트
        queryClient.setQueryData(`/user/${userId}`, profile);
      };
    
      return (
        <div>
          {/* 프로필 수정 UI */}
        </div>
      );
    };
    export default Profile;

     

     

     


    ✅ 페이징 처리 기법

     

    TanStack Query의 페이징처리 기법을 통해 페이지네이션 처리와 더 나아가 인피니티 스크롤을 구현하기 위해 사용되는 hook인 useInfiniteQuery 기본적인 사용법을 알아보자.

     

    1️⃣ useInfiniteQuery

    const {
    	data,
    	hasNextPage,
    	fetchNextPage,
        fetchPreviousPage,
        hasPreviousPage,
    	isFetching,
    } = useInfiniteQuery(고유키, fetcher함수, {
    	initialPageParam: 1,
    	getNextPageParam: (lastPage)=> lastPage.nextCursor,
        getPreviousPageParam: (firstPage) => firstPage.PrevCursor,
    })
    • useQuery와 받는 인자는 동일하다.
    • 옵션에서 getNextPageParam과 getPreviousPageParam을 통해 페이지에 대한 정보를 전달함으로써 이전 혹은 이후의 페이지에 대한 데이터를 가져올 수 있게 된다.
    • 이후의 값을 불러오기 위해 fetchNextPage를 호출하는 로직을 통해 이후의 값을 가져올 수 있으며 fetchPreviousPage를 호출하는 로직을 통해 이전의 값을 가져올 수 있다.

     

    2️⃣ 페이지네이션과 인피니티 스크롤의 차이점

    함수를 호출하는 Trigger의 차이라고 생각되며 Observer pattern을 통해 인피니티 스크롤을 구현하는 방법이 있다.

     

     


    ✅ Optimistic Update

     

    단순히 Mutations를 통해서 update를 하게된다면 사용자의 요청이 완료되고 업데이트 된 데이터가 화면에 그려지는 순서로 이루어지는데 이렇게되면 요청시간만큼의 딜레이가 UX를 떨어뜨리게 되므로 사용자의 인터렉션에 대해 즉각적으로 화면에 반영된 후 요청의 성공 / 실패 여부에 따라 이전 상태로 되돌릴지 현재를 유지할지 결정하는 방법으로 낙관적으로 성공할 것을 생각하고 미리 UI에 반영한다는 의미이다.

     

    1️⃣ useMutation + queryClient

    useMutation 훅을 통해 데이터를 수정하는 API를 호출하여 낙관적 업데이트 로직인 onMutate를 통해 임시로 UI를 업데이트 하고, 롤백로직인 onError를 통해 에러가 발생했을 시 이전 상태로 롤백한다. 이후 성공시에 캐시 데이터를 수정해야하므로 onSettle 또는 onSuccess를 통해서 캐시를 새롭게 고친다.

     

    useMutation({
      mutationFn: updateTodo,
      // onMutate를 통해 낙관적으로 보여줄 UI에 대해서 처리를 한다
      onMutate: async (newTodo) => {
    
    	// 재요청(refech)중인 작업이 있다면 이를 취소해서 낙관적 업데이트 된 UI 상태를 덮어쓰는 것을 방지 
        await queryClient.cancelQueries({ queryKey: ['todos', newTodo.id] })
    
        // reject시에 되돌려야하므로 이전 값을 저장
        const previousTodo = queryClient.getQueryData(['todos', newTodo.id])
    
        // 낙관적으로 값을 업데이트 한다.(캐시 데이터를 바꾼다)
        queryClient.setQueryData(['todos', newTodo.id], newTodo)
    
        // 이전 값과 새로운 값을 포함하는 컨텍스트를 반환한다. 추후 onError나 onSettled에 사용하기 위해
        return { previousTodo, newTodo }
      },
      // 요청이 실패시 onMutate를 통해 반환받은 값을 사용해서 이전 상태로 되돌린다
      onError: (err, newTodo, context) => {
        queryClient.setQueryData(
          ['todos', context.newTodo.id],
          context.previousTodo,
        )
      },
      // 에러나 성공 후에는 항상 재요청을 하며 이부분을 onSuccess로 바꿔도 상관없다.
      onSettled: (newTodo) => {
        queryClient.invalidateQueries({ queryKey: ['todos', newTodo.id] })
      },
    })
    • queryClient는 캐시된 쿼리 데이터를 관리하는 객체로 캐시데이터를 업데이트 함으로 낙관적으로 업데이트 된다.
    • 이후 마지막 invalidatedQueires를 통해 현재 캐시 데이터를 무효화 시켜 새롭게 서버로 데이터를 받아오게끔함으로 서버 데이터와 캐시 데이터를 일치시킨다.

     

     

     

     

     

     

     

     

    📌 reference

    - TanStack Query Docs 

    - React-query vs SWR        

    '프로그래밍' 카테고리의 다른 글

    [Docker] Docker의 개념 및 활용  (0) 2024.05.15
    [git] 팀 프로젝트를 하기 위한 git 사용 전략  (0) 2024.04.18
    [Zustand] Zustand란?  (0) 2024.03.23

    댓글

Designed by Tistory.