티스토리 뷰

🚀 React-Query ?

React-Query는 서버로부터 받은 데이터의 전역 상태 관리를 위한 라이브러리입니다.

서버의 데이터 요청을 캐싱하여 지속적으로 동기화하고 업데이트 하는 작업을 도와줍니다.

 

사용하는 이유

postList를 get하는 요청을 해서 목록 페이지에 보여준다고 해봅시다. 그러면 우리는 useEffect(() => {}, []) 안에 get 요청을 하는 코드를 작성해서 화면에 렌더링이 되면 get 요청을 보내서 응답을 받고 그걸 다시 useState로 해둔 state로 저장하게 될 것입니다.

그런데 postList가 바뀌지 않았는데 화면이 마운트될 때 마다 요청을 보내고 받고 요청을 보내고 받고 하는 것은 불필요한 요청같아 보입니다.

React-Query에서는 이 서버로부터 받은 데이터를 캐싱해 두었다가 특정한 time 전까지는 다시 요청을 해서 화면렌더링 하는 것이 아니라, 캐싱해둔 값으로 화면에 보여주게 됩니다. 물론, 필요할 때는 다시 fetch 할 수도 있습니다. 즉, 불필요한 요청과 응답 과정이 줄어들게 되는 것인데요.

 

장점

  • 캐싱
  • get을 한 데이터에 대해 update를 하면 자동으로 get을 다시 수행한다 (예를 들어 게시판의 글을 가져왔을 때 게시판의 글을 생성하면 게시판 글을 get하는 api를 자동으로 다시 fetch)
  • 데이터가 오래 되었다고 판단되면 다시 get(invalidateQueries)
  • 동일 데이터에 대해 여러 번 요청 X
  • 무한 스크롤(Infinite Queries)
  • 비동기 과정을 선언적으로 관리할 수 있다
  • react hook과 사용하는 구조가 비슷하다

🚀 사용해보자

1. 라이브러리 설치

$ npm i @tanstack/react-query @tanstack/react-query-devtools

2.  new QueryClient를 통해 기본 설정을 위한 변수를 만든다

📜 App.js파일

const queryClient = new QueryClient();

3. 최상위에 QueryClientProvider로 감싸고, clinet 속성에 방금 만든 client 변수를 담는다

📜 App.js 파일

function App() {
  const queryClient = new QueryClient();
  
  return(
    <QueryClientProvider client={queryClient}>
    </QueryClientProvider>
  )
}

4. hooks/queries 폴더 안에 hook 형태의 쿼리 함수를 작성한다

React-Query는 hook 형태로 쿼리 함수를 작성합니다. 따라서 쿼리함수 앞에는 use가 붙습니다.

 

1) GET 요청일 때 → useQuery( queryKey, queryFn, options )

useQuery Hook은 요청마다 (API마다) 구분되는 쿼리 키(Query Key)라는 명칭으로 응답값을 로컬에 캐싱하고 관리합니다.

따라서 매번 요청을 보내는 것이 아니라, 응답 받은 데이터의 staleTime이라는 유효 시간이 끝나거나 inValid 상태가 되면 그 때 refetch를 시도합니다.

 

staleTime

데이터가 fresh(신선한) → stale(신선하지 않은) 상태로 변경되는데 걸리는 시간을 의미합니다.

fresh 상태일 때는 쿼리 함수가 새롭게 마운트 되어도 요청을 보내지 않습니다.

즉, 데이터가 한번 fetch되고 나서 staleTime이 지나지 않았다면 언마운트 후 마운트 되어도 fetch가 일어나지 않고, 마운트 시 캐싱되어 있는 데이터를 보여주게 되는 것입니다.

 

cacheTime

데이터가 inValid 상태일 때 캐싱된 상태로 남아있는 시간입니다.

쿼리 함수가 언마운트되면 데이터는 inValid 상태로 변경되며, 캐시는 cacheTime 만큼 유지됩니다.

cacheTime이 지나기 전에 쿼리 함수가 다시 마운트 되면, 데이터를 fetch하는 동안 캐시 데이터를 보여주게 됩니다.

cacheTime은 staleTime과 관계없이, 무조건 inValid된 시점을 기준으로 캐시 데이터 삭제를 결정합니다.

 

(0) 쿼리 함수를 작성하자(useQeury)

이 쿼리 함수를 실행하게 되면, 내부 콜백함수와 옵션을 거친 data, error, status, isLoading이 리턴하게 됩니다.

const useGetTodo = () => {
  const { data, error, status, isLoading } = useQuery(
    [queryKey], 
    queryFn, 
    {
      options
    },
  )
  return { data, error, status, isLoading }
}

(1) 쿼리 key를 작성한다 ← useQuery의 첫 번째 인자

이 키 값을 가지고 해당 쿼리 함수에서 비동기 통신으로 받은 데이터의 상태를 관리하게 됩니다.

따라서 오탈자가 있으면 절대 안되는데요. 그래서 따로 consts라는 폴더를 만들어 키값을 관리하기도 합니다.

 

📜 consts/query-key.js 파일

export const QUERY_KEY = {
  GET_TODO: 'GET_TODO'
}

다시 쿼리 함수로 돌아가 작성을 이어서 합니다.

const useGetTodo = () => {
  const { data, error, status, isLoading } = useQuery(
    [QUERY_KEY.GET_TODO], 쿼리함수, 옵션
  )
  return { data, error, status, isLoading }
}

(2) 콜백 함수를 작성한다 ← useQuery의 두 번째 인자

쿼리 함수가 호출되었을 때 진행될 비동기 통신 로직을 콜백함수 형태로 작성하면 됩니다.

const useGetTodo = () => {
  const { data, error, status, isLoading } = useQuery(
    [QUERY_KEY.GET_TODO],
    () => TodoApi.getTodo(),
    옵션
  )
}

(3) 옵션을 작성한다  ← useQuery의 세 번째 인자

서버로부터 받은 데이터를 업데이트하는 옵션을 작성하게 됩니다.

다음과 같은 속성을 작성할 수 있습니다.

 

  • refetchOnWindowFocus(boolean | "always" )
    데이터가 stale 상태일 경우, 윈도우가 포커싱될 때마다 refetch 할건지의 속성
  • retry(boolean | number | (failureCount: number, error: TError) => boolean)
    실패한 쿼리를 재시도하는 옵션. 기본적으로 쿼리 실패 시 3번 재시도. true로 설정하면 실패 시 무한 재시도하고, false로 하면 재시도를 하지 않는다
  • staleTime(number | Infinity)
    staleTime은 데이터가 fresh 상태로 유지되는 시간이다. 해당 시간이 지나면 stale 상태가 된다. 기본값은 0이다.
  • cacheTime(number | Infinity)
    cacheTime은 inValid 상태인 캐시 데이터가 메모리에 남아있는 시간이다. 이 시간이 지나면 캐시 데이터는 가비지 컬렉터에 의해 메모리에서 삭제된다. 기본값은 5분이다.
  • refetchOnMount(boolean | "always")
    데이터가 stale 상태일 경우 마운트 시 마다 refetch를 실행하는 옵션. 기본값은 true. always로 설정하면 마운트 시 마다 매번 refetch를 실행한다
  • onSuccess((data) => void)
    쿼리 성공 시 실행되는 함수. 매개변수 data는 성공 시 서버에서 넘어오는 응답값
  • onError((error) => void)
    쿼리 실패 시 실행되는 함수. 매개변수로 에러 값을 받을 수 있다
const useGetTodo = () => {
  const { data, error, status, isLoading } = useQuery(
    [QUERY_KEY.GET_TODO],
    () => TodoApi.getTodo(),
    {
      regetchWindowFocus: false,
      retry: 1,
      cacheTime: 1000 * 60 * 5, // 5분
      onSuccess: () => {...},
      onError: () => {...},
    }
  )
  return { data, error, status, isLoading }
}

(4) get 해온 데이터를 사용해보자

const { data: todoList, error, status, isLoading } = useGetTodo();

todoList.map(...)

2) POST, PUT, DELETE 요청 때 → useMutaion를 리턴하는 함수, mutateAsync

방법1 ) 로직을 분리할 때 : useMutation

const use쿼리함수이름 = () => {
  // ...
  return useMutation((mutate를 통해 받은 매개변수) => {비동기 통신 로직...}, {
    onSuccess: (res) => {...},
    onError: (error) => {...}
  })
}

ex. 📜 hooks/queries/user-login.js 파일

const useUserLogin = () => {
  const auth = useAuth(); // 여기에는 로그인 로직(로그인 하면 토큰이 담긴다 등의 로직이 같이 담겨있는 함수가 존재)
  
  return useMutation(({ email, password }) => AuthApi.login(email, password), {
    // 위 비동기 통신이 성공/실패했을 때 로직을 작성
    onSuccess: (res) => {
      auth.login(res.data.token);
    },
    onError: (err) => {
      console.log(err);
    }
  });
}

사용할 때는 위 비동기 통신을 통해 받은 데이터가 필요한 컴포넌트 안에서 다음과 같이 작성하면 됩니다.

const { mutate } = useUserLogin();

우리는 useUserLogin이라는 함수를 작성했었습니다. 이 함수는 useMutation()을 리턴하는 함수였습니다. 자세히 useMutation 안에서는 비동기 통신을 하고, 그 통신이 성공했을 때와 실패했을 때 로직도 작성되어 있습니다.

따라서 mutate에게 필요한 body 내용을 전달해주면 그 비동기 통신 로직이 실행되고, 성공, 실패 시 코드까지 실행되게 됩니다.

mutate를 배달원 같은 역할이라고 생각이 듭니다 :)

 

그런데 mutate 에는 async await를 사용할 수 없습니다.

방법2 ) 내부에 로직 같이 작성할 때: asyncMutate

이번에도 방법1과 동일하게 useMutation 안에 비동기 통신 로직을 작성합니다. 다만, 성공/실패 시 로직을 함께 작성하지는 않습니다.

 

mutateAsync를 선언할 때 useMuation에 비동기 통신 로직을 작성해서 변수를 만들어줍니다.

const { mutateAsync } = useMutation((매개변수) => { 비동기 통신 로직... })

ex.

const { mutateAsync } = useMutation(({ email, password }) => {
  AuthApi.signup(email, passowrd)
})

사용할 때는 우리가 axios 요청을 try-catch 문 안에서 사용하듯이 async-await를 통해 비동기 작업을 코드로 작성해 주면 됩니다.

const onClickSignup = async () => {
  try {
    const res = await mutateAsync({ email, password });
  } catch (err) {
    console.log(err)
  }
}

3) POST, PUT, DELETE 요청 + get되어있는 데이터를 refetch 시켜야할 때
    → queryClient.invalidateQueries(쿼리key)

get요청시 useQuery를 통해 전역으로 백엔드 데이터를 관리하는 쿼리 함수를 작성했었습니다. 그런데 이 데이터는 staleTime이 0이 되기 전 혹은 inValid 상태가 아니라면 재요청을 보내지 않습니다.

그런데, 예를 들어 전역으로 관리하고 있는 백엔드 데이터 todoList 데이터에 addTodo 하는 경우에는 이 todo가 추가되고서 todoList를 재요청해야 합니다(그래야 추가된게 보일테니까요).

따라서 addTodo할 때는 getTodo를 inValid 상태로 만들어버려서 재요청을 받게끔 해야하는 것입니다.

 

이 경우에도 POST, PUT, DELETE 요청일테니 함수를 작성하는데 useMutation을 리턴하는 함수를 작성해주면 됩니다.

const useAddTodo = () => {
  const queryClient = useQueryClient(); // 캐싱되어 있는 데이터를 불러옴
  
  return useMutation((todo) => TodoApi.addTodo(add), {
    onSuccessL () => {
      // 위 콜백함수가 성공하게 되면 --> 데이터가 잘 추가되었다는 말이므로
      // 쿼리 키값을 넣어서 inValid 상태를 만들어줌
      queryClient.inValidateQueries(QUERY_KEY.GET_TODO);
    }
  })
}

 

같이 보면 좋을 site

 

카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유 | 카카오페이 기술 블로그

카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유에 대해 설명합니다. 이 글은 연작 중 1편에 해당합니다. 1편: 카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유, 2편: React Que

tech.kakaopay.com

 

 

TanStack Query | React Query, Solid Query, Svelte Query, Vue Query

Powerful asynchronous state management, server-state utilities and data fetching for TS/JS, React, Solid, Svelte and Vue

tanstack.com

 

728x90
LIST
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함