Just Do IT!

[React] React Query 본문

개발 공부/React

[React] React Query

MOON달 2023. 1. 10. 16:48
728x90
React Query를 사용하는 이유?
  • 원래 다른 서버와의 API 통신과 비동기 데이터 관리를 위해 Redux-thunk, Redux-Saga 등 미들웨어를 사용했었다.
  • 클라이언트 쪽의 데이터들을 관리하기에 적합해도 서버 쪽의 데이터들을 관리하기에는 적합하지 않아서 불편한 점이 있엇다. (Redux는 비동기 데이터 관리를 위한 전문 라이브러리가 아니다)
  • 서버 데이터와 클라이언트 데이터 분리에 용이하다.
  • React Query 라이브러리를 사용하면 편리하다.

 

 

 

 

 

 

서버 데이터와 클라이언트 데이터
  • 서버 데이터
    • 여러 사람들이 동시에 공유할 수 있는 데이터
    • 서버라는 공간에서 계속 쓰이는 데이터를 뜻한다.
    • 특정 시점에 클라이언트의 요청에 대해 데이터 베이스에서 유저 정보를 가져와 서버의 상태 값을 만들어낸다
    • 데이터 베이스에 있는 값을 그대로 클라이언트에게 전달 할 수도 있고, 요청에 담긴 특정 값을 이용해 정보를 가공 해서 메모리에 들고 있는다
    • 이러한 정보를 클라이언트에게 제공한다.
  • 클라이언트 데이터
    • client에서 자체적으로 만드는 state (최초 데이터의 발생지가 클라이언트)
    • server에서 전달받은 값으로 만다는 state (최초 데이터의 발생지가 서버)
    • 사용자의 편의성을 위해 관리가 필요한 데이터를 뜻한다.

☞ React Query는 서버에서 가져온 데이터를 웹 브라우저 앱에서 사용하기 쉽게 도와주는 기술이다

 

 

 

 

 

 

React Query 설치
$ npm i react-query --save
$ yarn add react-query

 

 

 

 

 

공식문서에서 제공하는 Quick Start
 import {
   useQuery,
   useMutation,
   useQueryClient,
   QueryClient,
   QueryClientProvider,
 } from 'react-query'
 import { getTodos, postTodo } from '../my-api'
 
 // Create a client
 const queryClient = new QueryClient()
 
 function App() {
   return (
     // Provide the client to your App
     <QueryClientProvider client={queryClient}>
       <Todos />
     </QueryClientProvider>
   )
 }
 
 function Todos() {
   // Access the client
   const queryClient = useQueryClient()
 
   // Queries
   const query = useQuery('todos', getTodos)
 
   // Mutations
   const mutation = useMutation(postTodo, {
     onSuccess: () => {
       // Invalidate and refetch
       queryClient.invalidateQueries('todos')
     },
   })
 
   return (
     <div>
       <ul>
         {query.data.map(todo => (
           <li key={todo.id}>{todo.title}</li>
         ))}
       </ul>
 
       <button
         onClick={() => {
           mutation.mutate({
             id: Date.now(),
             title: 'Do Laundry',
           })
         }}
       >
         Add Todo
       </button>
     </div>
   )
 }
 
 render(<App />, document.getElementById('root'))
  • useQuery, useMutation 등 다양한 hook을 제공한다.
  • 기존에는 state 관리가 필요했지만 react query에서는 필요하지 않다.

 

 

 

 

 

useQuery()
  • 어떤 데이터에 대한 요청을 의미한다
  • axios의 경우 get 요청과 비슷하다.
  • 첫 번째 인자 : Query Keys
    • refetching하는데 쓰임
    • 애플리케이션 전체 맥락에서 이 쿼리를 공유하는 방버으로 쓰임
    • 어떤 컴포넌트에서도 같은 key면 같은 쿼리 및 데이터 보장
    • 한 단어, 배열, 객체 등이 key가 될 수 있다 (unique해야 한다)
    • api 로직과는 전혀 관계 없다
  • 두 번째 인자 : Query Function (쿼리 함수)
    • 쿼리 함수는 promise 객체를 return 한다
    • promise 객체에 담기는 주요한 상태 정보
      • 대기 (pending) : 요청한 직후 아직 성공(resolve) 혹은 실패(rejected)되지 않은 상태
      • 이행 (fulfilled) : 정상적으로 전달을 해준 상태
      • 거부 (rejected) : 데이터를 전달 못해준 상태
    • promise 객체는 반드시 data를 resolve 하거나 error를 발생시킨다.
  • useQuery를 통해 얻은 결과물은 객체(Object)이다
    • 그 안에는 '조회'를 요청한 결과에 대한 거의 모든 정보가 들어 있다.
    • 시작하면 isLoading = true
    • 조회 결과 오류 발생 : isError = true / isLoading = false
    • 조회 결과 정상 : isSuccess = true / isLoading = false (data 객체를 통해 좀 더 상세한 조회 결과 확인)
  • 예제 (todo)
import { useQuery } from 'react-query';

function TodoList({ isActive }) {
	const { isLoading, isError, data, error } = useQuery(['todos';, { isDone: isActive }], getTodos);

	if (isLoading) {
		return <p>로딩중입니다</p>;
	)

	if (isError) {
		console.log("error : ", error);
		return <p>오류가 발생하였습니다</p>;
	)
    
    ...

 

 

 

 

useQueries()
  • 여러 개의 useQuery를 쓰고 싶을 때 useQuery를 사용하면 다른 쿼리가 실행되기 전에 첫번째 쿼리가 중단하고 에러를 반환할 수 있다.
  • 여러개의 useQuery를 나란히 사용할 수 있다
  • Query Option 객체를 배열로 받을 수 있고, 마찬가지로 결과값 또한 배열로 return 해준다.
function App({ users }) {
   const userQueries = useQueries(
     users.map(user => {
       return {
         queryKey: ['user', user.id],
         queryFn: () => fetchUserById(user.id),
       }
     })
   )
 }

 

 

 

 

useMutation
  • 어떤 데이터(=데이터의 그룹)를 변경
  • 추가, 수정, 삭제가 가능하다는 의미
  • axios의 post, put, patch, delete 요청과 비슷하다
  • useMutation()의 첫번째 인자인 mutationFn는 필수이다
  • optimistic update을 활용해서 미리 UI부터 갱신할 수 있다
  • invalidateQueries 메소드 및 setQueryData 메소드랑 같이 사용하면 최고의 효율을 낼 수 있다.
    • invalidateQueries : 원래의 data 대신 data를 갱신해서 가져올 수 있다
    • setQueryData : 기존에 queryKey에 매핑되어 있는 데이터를 새롭게 정의해준다 (invalidateQueries 사용 안해도 된다)
  • Option
    • onMutate: (variables) => Promise
      • mutation() 이 실행하기 전 먼저 실행되고 mutation()함수가 전달받은 파라미터가 동일하게 전달됩니다.
    • onSuccess: (data, variables, context) => Promise
      • mutation()이 성공하면 결과를 전달할 때 실행 됩니다.
    • onError: (err, variables, context) => Promise
      • mutation()과정에서 에러가 발생되면 실행됩니다.
    • onSettled: (data, error, variables, context) => Promise
      • mutation()의 성공 / 에러 상황에 따라 해당 데이터를 전달받습니다.
  • 예제
import { QueryClient, useMutation } from 'react-query';

function Input() {
...

	const queryClient = new QueryClient();

	const mutation = useMutation(addTodo, {
		onSuccess: () => {
			// Invalidate and refresh
			// 이렇게 하면 todos라는 이름으로 읽었던 data가 최신이 아니므로 invalidate할 수 있음
			// TodoInput.jsx 파일의 useQuery의 querykey 이름으로 데이터를 갱신해서 가져옴
			queryClient.invalidateQueries('todos');  // 서버데이터에 의존하고 있음
}

...

	// 삭제하는 부분
	const removeTodoMutation = useMutation(removeTodo, {
		onSuccess: () => {
			queryClient.invalidateQueries('todos');
		},
	});
    
    ...

 

 

 


React Native 강의를 들으면서도 정리했었는데 그거는 native에서 사용하는 거라 조금 다른 것 같아서

아예 특강도 들은 겸 따로 글로 정리해봤다.

 

이외의 내용들은 나중에 공부하면서 추가하거나 공식 문서에서 보면 될 듯 하다.

 

https://react-query-v3.tanstack.com/