react-Query

정태수·2023년 10월 25일
0
post-thumbnail

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

React-Query가 만들어진 이유

  • 서버 쪽의 데이터들을 좀더 쉽게 관리하기 위해
    ✔️ 기존에 Redux, Mobx, Recoil과 같은 다양하고 훌륭한 상태 관리 라이브러리들이 있긴 하지만, 클라이언트 쪽의 데이터들을 관리하기에 적합할 순 있어도 서버 쪽의 데이터들을 관리하기에는 적합하지 않은 점들이 있다.

이 점을 해결하기 위해 React-Query가 만들어졌다.

React-Query의 장점
✔️ 위에서 설명한 것처럼 React 어플리케이션 내에서 데이터 패칭, 캐싱, 동기적, 그리고 서버의 상태의 업데이트를 좀 더 용이하기 위해 만들어준다.

✔️ 기존에는 직접 만들어서 사용했던 기능들을 별도의 옵션으로 지원하여 복잡하고 이해할 수 없는 수많은 코드를 대신 React-Query 로직을 통해 짧은 코드로 대체할 수 있게 되었다.

✔️ 프로젝트 구조가 기존보다 단순해져 애플리케이션을 유지 보수하기 쉽고, 새로운 기능을 쉽게 구축할 수 있다.

그 외에도 많은 장점이 있다.

→ React Query는 별도의 설정 없이 즉시 사용 가능하다

→ 캐싱을 효율적으로 관리해준다

→ 같은 데이터에 대한 여러번의 요청이 있을 시 중복을 제거한다.

→ 백그라운드에서 알아서 오래된 데이터를 업데이트해준다.

→ 데이터 업데이트 시 최대한 빨리 반영한다.

→ 페이징처리, 지연 로딩 데이터와 같은 성능 최적화해준다

→ 서버 쪽 데이터를 가비지 컬렉션을 이용하여 자동으로 메모리를 관리해준다.

→ 구조적 공유를 통해 쿼리의 결과를 기억해준다.

사용예시

import { useQuery } from 'react-query'
 
 function App() {
   const info = useQuery('todos', fetchTodoList)
 }
  • Query는 GET, POST 메소드 포함을 기반으로 서버에서 데이터를 패칭하기 위한 비동기 함수와 사용된다.

  • 'todos': 첫번째 인자로, unique key를 명시해준다.
    ✔️ 해당 key는 내부적으로 데이터 재요청, 캐싱, 쿼리를 공유하기 위해 사용된다.

  • fetchTodoList: 두번째 인자에는 우리가 요청할 비동기 함수를 넣어주는데, 데이터와 error를 return해준다.

  • useQuery return값에 포함되어 있는 states

주요 states

  • isLoading or status === 'loading' : 현재 데이터를 요청 중이나 아직 데이터가 없을 경우
  • isError or status === 'error': 쿼리에서 에러가 났을 경우
    ☞error : 해당 property로 에러 메세지를 확인할 수 있다.
  • isSuccess or status === 'success' - 쿼리 요청 성공
    ☞ data : 해당 property로 성공한 데이터를 확인할 수 있다.
  • isIdle or status === 'idle': 이 쿼리는 현재 사용할 수 없을 때 나옴
  • isFetching : 데이터 요청 중일 때는 (내부적으로 리패칭 중 일때도 포함) 항상 True를 리턴한다

사용방법

  • 방법 1. boolean 타입으로 체크하기
   function Todos() {
   const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)
    
    if (isLoading) {
      return <span>Loading...</span>
    }
   
    if (isError) {
      return <span>Error: {error.message}</span>
    }
   
    // We can assume by this point that `isSuccess === true`
    return (
      <ul>
        {data.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    )
   }

방법 2. status 하나로 꺼내서 사용할 수도 있음

function Todos() {
   const { status, data, error } = useQuery('todos', fetchTodoList)
 
   if (status === 'loading') {
     return <span>Loading...</span>
   }
 
   if (status === 'error') {
     return <span>Error: {error.message}</span>
   }
 
   // also status === 'success', but "else" logic works, too
   return (
     <ul>
       {data.map(todo => (
         <li key={todo.id}>{todo.title}</li>
       ))}
     </ul>
   )
 }

Query Keys
✅ React Query는 query key로 캐싱을 관리한다.

✅ 간단한 문자열이어도 되고, 배열 형태, 중첩된 객체와 같이 복잡하든 상관없다

✅ Query key가 순차적 진행을 보장하는 직렬화 기법으로 쿼리의 데이터는 유일하다.

✔️ Query key가 문자열일 때

// A list of todos

 useQuery('todos', ...) // queryKey === ['todos']
 
 // Something else, whatever!
 useQuery('somethingSpecial', ...) // queryKey === ['somethingSpecial']

✔️ Query key가 배열일 때

// An individual todo
 useQuery(['todo', 5], ...)
 // queryKey === ['todo', 5]
 
 // An individual todo in a "preview" format
 useQuery(['todo', 5, { preview: true }], ...)
 // queryKey === ['todo', 5, { preview: true }]
 
 // A list of todos that are "done"
 useQuery(['todos', { type: 'done' }], ...)
 // queryKey === ['todos', { type: 'done' }]
✔️ Query key에 있는 object들의 순서는 중요하지 않다.
동일한 Query key -> array의 object내 에 있는 값들의 순서는 중요하지 않음 (동일함)
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)
동일 하지 않은 Query key. -> array 값의 순서는 중요하다
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
✔️ Query key는 파라미터를 사용해도 된다.

파라미터가 unique한 값이라면 인자로 받아 아래와 같이 사용할 수 있다.

function Todos({ todoId }) {
   const result = useQuery(['todos', todoId], () => fetchTodoById(todoId))
 }

Query Functions

✅ Query Function은 promise를 return하는 함수

✅ Promise는 data를 return하거나 에러가 나면 에러를 return한다.

아래와 같이 function을 작성할 수 있다.

useQuery(['todos'], fetchAllTodos)
useQuery(['todos', todoId], () => fetchTodoById(todoId))
useQuery(['todos', todoId], async () => {
   const data = await fetchTodoById(todoId)
   return data
 })
useQuery(['todos', todoId], ({ queryKey }) => fetchTodoById(queryKey[1]))

✅ Query Function에서 발생된 에러는 query의 error state에 남는다.

const { error } = useQuery(['todos', todoId], async () => {
   if (somethingGoesWrong) {
     throw new Error('Oh no!')
   }
 
   return data
 })

✅ fetch와 같이 기본적으로 Http 요청 실패에 대한 에러를 제공하지 않는 경우 아래와 같이 직접 error를 던져줘야한다.

✔️ axios와 graphql-request과 같은 대부분의 함수가 기본적으로 HTTP 요청이 실패하면 에러를 반환하지만,

fetch와 같이 몇몇 함수는 오직 network error에 대해서만 에러를 내뱉는 경우가 있다.

이 경우에 아래와 같은 방법으로 에러를 직접 반환해줘야한다.

useQuery(['todos', todoId], async () => {
   const response = await fetch('/todos/' + todoId)
   if (!response.ok) {
     throw new Error('Network response was not ok')
   }
   return response.json()
 })

✅ Query Function Variables

Query keys는 데이터를 요청하는동안 식별한는 것 뿐만아니라 또한 필요할 때 편리하게 Query Function에서 사용도 할 수 있다.

이렇게 하면, 필요할 때마다 아래와 같이 Query Function 에서 추출해서 사용할 수 있다.

function Todos({ status, page }) {
   const result = useQuery(['todos', { status, page }], fetchTodoList)
 }
 
 // Access the key, status and page variables in your query function!
 function fetchTodoList({ queryKey }) {
   const [_key, { status, page }] = queryKey
   return new Promise()
 }

즉 _key는 'todos' 즉 식별자를 개발자가 확실하게 키값으로 인지하기위해서 편하게 넣은 값 나머지는 비구조화 할당을 통해서 데이터 가공이 가능하다.

📍 Dependent Queries: 동기적으로 사용하기
✅ enabled: true이면 특정 조건이 trued 때만 실행하겠다

→ 이전 함수가 끝나고 다음 것이 영향을 받을 때 enabled option을 사용하면 좋다.

const { data: user } = useQuery(['user', email], getUserByEmail)
 
 const userId = user?.id
 
 // Then get the user's projects
 const { isIdle, data: projects } = useQuery(
   ['projects', userId],
   getProjectsByUser,
   {
     // The query will not execute until the userId exists
     enabled: !!userId, 
   }
 )

📍 Loading state 전역으로 사용하기
✅ useIsFetching

→ useQuery 내부 isLoading state 말고 backgroud에서의 fetching 체크가능하다

import { useIsFetching } from 'react-query'
 
 function GlobalLoadingIndicator() {
   const isFetching = useIsFetching()
 
   return isFetching ? (
     <div>Queries are fetching in the background...</div>
   ) : null
 }
profile
프론트엔드 개발자

0개의 댓글