본문 바로가기

React/Hooks

React Query를 이용해서 API를 쉽게 연동하는 방법

 

리액트의 상태를 관리하는 state는 핵심 개념이다.

리액트 개발자가 되려면 state에 대해서 빠삭하게 알아야 한다.

그러나 리액트는 기본적으로 서버 데이터를 state처럼 다루지는 않는다.

개발자가 useEffect 등을 써서 state에 직접 넣어줘야 한다.

 

만약 서버 데이터를 state처럼 다룰 수 있다면

코드도 짧아지고 좀 더 리액트스럽게 개발을 할 수 있다.

state가 바뀔 때만 리렌더링 되는 것처럼

서버 데이터가 바뀔 때만 리렌더링 된다면 

서버 데이터 역시도 state처럼 상태 관리가 된다.

 

이를 가능하게 해주는 라이브러리 중 하나가 React Query다.

리액트 쿼리는 훅으로서 다음과 같은 형태로 쓸 수 있다.

 

const App = () => {
  const { data } = useQuery('todos', getTodos);
  
  return (
    <div>
      {data?.map(todo => <div key={todo}>{todo}</div>}
    </div>
  )
}

 

Promise를 리턴하는 getTodos api 함수는 todos 배열을 리턴한다.

이 함수를 위처럼 useQuery의 두 번째 인자에 넣으면 data로 todos 배열을 받을 수 있다.

앱이 제일 처음 시작하면 getTodos api 함수가 데이터를 비동기적으로 받아오기 전이기 때문에

data의 값은 undefined가 되고 getTodos의 요청이 완료 됐을 때

data에 값이 들어오면서 리렌더링된다.

(redux의 훅인 useSelector를 썼을 때와 비슷하게 작용한다.)

 

더 이상 useEffect 안에 api 함수를 넣고 setState로 초기화 할 필요없이 

useQuery라는 훅 하나로 data를 받아 올 수 있고,

state를 따로 정의하지 않아도 된다.

data가 곧 state라고 생각해도 좋다.

 

리액트 쿼리의 key

useQuery의 첫 번째 인자에 들어가는 string 값은 뭘까?

data라는 state의 고유한 id라 할 수 있다.

보통 앱에서 data 요청은 여러번 일어난다.

이 data들을 관리하기 위해서 id가 필요하다.

 

만약 위에서 'todos'라고 표시를 한 getTodos가 리턴하는 데이터 값이 지금 상황에서는 바뀌지 않는다는 게 보장된다면

리액트 앱 내에서 저장하고 있다가(캐싱)

다른 페이지에 갔다 돌아오는 등의 데이터가 필요한 상황에서 getTodos를 재요청할 필요없이 

저장해 뒀던 'todos'의 id를 갖고 있는 data를 슥 내밀면 된다.

이렇게 리액트 쿼리는 캐싱을 손쉽게 할 수 있다는 장점이 있다.

(단, staleTime과 cacheTime 설정을 해줘야 한다.)

 

리액트 쿼리를 이용하면 api 연동을 쉽게 할 수 있는데

다음과 같은 패턴이 괜찮아 보여서 소개하고 마치려 한다.

 

보통 REST API에서는 get 메소드에 대응하여 post, put, patch 메소드가 짝을 이룬다.

예를 들어 todo 리스트는 get 요청으로 받아오고

todo 리스트의 item 하나를 새로 만들거나 수정하려면 post, put, patch 메소드를 사용한다.

 

이때 재미있는 사실은 post, put, patch 메소드 등을 이용해서 데이터를 수정할 때마다

get 메소드 요청이 필요하다는 사실이다.

간단한 앱이야 post 이후에 변경된 data를 response로 받아 변경된 todo item을 업데이트 할 수도 있겠지만

매번 그렇게 해주는 게 쉬운 일은 아니다.

 

대신 data가 변경될 때마다 쌍을 이루는 get 메소드가 호출되게 하는 게 코드도 간단하고 편하다.

물론 get 요청을 한 번 더 한다는 점에서 2배로 서버의 리소스를 잡아 먹는다는 단점이 있긴하다.

개발 비용을 줄일 것인지, 서버 리소스를 줄일 것인지의 문제일까?

 

개인적으로는 단순한 코드로 개발 비용과 유지 보수가 쉬워진다는 점에서 전자를 택하겠다.

만약 전자의 방식을 따른다면 리액트 쿼리로 정말 간단히 해결할 수 있다.

 

앞서 'todos'라는 키로 서버 데이터를 구분한다고 했다.

post 등으로 todo 리스트의 데이터에 변화가 생겼을 때 리액트 쿼리의 invalidateQueries 메소드에 

'todos'라는 키 값을 넣어주면 된다.

그러면 리액트 쿼리 라이브러리 내부적으로 'todos'에 해당하는 서버 데이터가 유효하지 않은 데이터라는 것을 알고

다시 데이터 요청(refetching)을 하게 된다.

 

아래처럼 useMutation이라는, 리액트 쿼리의 post, put, patch용 훅을 이용해서 (데이터를 수정)

요청에 성공했을 때 'todos'를 invlidateQueries 메소드를 써서 다시 받아올 수 있도록 한다.

 

// Access the client
const queryClient = useQueryClient()
 
// Queries
const { data } = useQuery('todos', getTodos)
 
// Mutations
const mutation = useMutation(postTodo, {
  onSuccess: () => {
    // Invalidate and refetch
    queryClient.invalidateQueries('todos')
  },
})

 

이렇게 하면 서버 데이터를 정말 state처럼 쓸 수 있게 된다.

get 메소드로 받아온 데이터를 상태로서 들고 있다가 

post, put, patch 등으로 상태 변화가 필요할 때 invalidateQueries를 써서 새로운 데이터로 리렌더링을 한다.

 

참고 :

https://react-query.tanstack.com/quick-start

https://react-query.tanstack.com/guides/query-invalidation#query-matching-with-invalidatequeries