이 글은 아래 링크를 번역한 글입니다.
https://blog.bitsrc.io/how-to-start-using-react-query-4869e3d5680d
----
리액트 애플리케이션을 만들 때 직면하는 과제 중 하나는 서버로부터 데이터를 가져오는 패턴을 결정하는 것입니다. 리액트에서 데이터를 가져오는 패턴 중 가장 일반적인 방법은, fetch 명령의 현재 상태를 알기 위해서 상태(State)를 사용하는 것입니다.
아래는 Star Wars API로부터 데이터를 가져오는 예제입니다.
import React, {useState, useEffect} from 'react';
import axios from 'axios';
// regular fetch with axios
function App() {
const [isLoading, setLoading] = useState(false)
const [isError, setError] = useState(false)
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async () => {
setError(false);
setLoading(true);
try {
const response = await axios('http://swapi.dev/api/people/1/');
setData(response.data);
} catch (error) {
setError(true);
}
setLoading(false);
};
fetchData()
}, []);
return (
<div className="App">
<h1>React Query example with Star Wars API</h1>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading ...</div>
) : (
<pre>{JSON.stringify(data, null, 2)}
</pre>
)}
</div>
);
}
export default App;
위 코드는 useState와 useEffect 두 개의 훅이 필요하고, 데이터를 저장할 세 가지의 다른 상태를 사용하며, API로부터 데이터를 가져오는 중인지 에러가 발생했는지를 확인합니다. 이 패턴의 문제는 데이터를 가져오는 로직의 대부분에서 계속 반복된다는 것입니다.
그게 다가 아닙니다. 데이터를 가져오는 데 가장 흔한 문제는 다음과 같습니다.
- 데이터는 애플리케이션 모든 곳에서 공유될 수 있고, 다른 사람이 변경할 수 있음
- 데이터가 최신 상태가 아닐 수 있으며, 새로 고쳐야 할 수 있음
- 요청 작업을 최적화하기 위해서 데이터를 무효화하고 캐싱 처리
마지막으로, 테마와 사이드바 설정 같은 유저의 설정 정보를 저장하는 로컬 상태와 API로부터 데이터를 가져와 저장하는 원격 상태를 결합하는 문제가 있습니다.
//what global state commonly look like nowadays
const state = {
theme: "light",
sidebar: "off",
followers: [],
following: [],
userProfile: {},
messages:[],
todos:[],
}
로컬 상태와 원격 상태를 분리해주면 좋지 않을까요? 그리고 데이터를 가져오는 데 작성해야 하는 보일러플레이트의 양을 줄일 수 있다면 어떨까요?
해결책 중 하나는 데이터를 가져오고 처리하는 커스텀 훅을 만드는 것이며, 이는 확실히 좋은 해결책입니다. 또한 Bit처럼 컴포넌트 허브에서 이 훅을 공유하고 관리할 수 있습니다. 이 방법은 한 워크스페이스의 어떤 프로젝트에도 사용할 수 있을 것입니다.(여기서 커스텀 훅은 순수한 훅일 수도 있지만, "스마트 컴포넌트"일 수도 있습니다.)
이 글에서 자세히 살펴볼 또 다른 해결책으로는 React Query입니다. 이 라이브러리는 두 개의 간단한 훅과 하나의 유틸리티 함수를 제공해서 작성해야 할 코드를 줄여주고, 원격 데이터를 fetch, synchronize, update, 그리고 cache하는 데 도움을 줄 것입니다.
이 글을 최대한 활용하려면, 제가 수정한 샘플 레파지토리를 다운로드 해주세요.
https://github.com/nsebhastian/react-query-example
이 작은 리액트 애플리케이션은 axios를 사용해 API로부터 string 배열을 가져올 것입니다. 화면에 보이는 <form>을 사용해서 배열에 새로운 string을 추가할 수 있습니다. 또한 React Query DevTools가 있기 때문에 캐시된 데이터를 실시간으로 확인할 수 있습니다.
useQuery hook
useQuery 훅은 React Query 라이브러리에서 데이터를 가져오는 코드를 등록하는 데 사용되는 함수입니다. 임의의 key string과 비동기 함수를 인자로 전달하면, 사용자에게 현재 애플리케이션 상태를 알려주는 데 사용할 수 있는 여러가지 값들을 반환합니다.
예를 들어, 이전의 Star Wars API 예제를 재구성 해보겠습니다.
import React from 'react';
import axios from 'axios';
import {useQuery} from 'react-query';
// react-query fetch with axios
function App() {
const { isLoading, error, data } = useQuery('fetchLuke', () =>
axios('http://swapi.dev/api/people/1/'))
return (
<div className="App">
<h1>React Query example with star wars API</h1>
{error && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Retrieving Luke Skywalker Information ...</div>
) : (
<pre>{JSON.stringify(data, null, 2)}
</pre>
)}
</div>
);
}
export default App;
이 코드에선 일반적인 useState와 useEffect를 사용하지 않았음에 주목해주세요. 두 훅을 사용하지 않은 이유는 useQuery에서 isLoading, error 응답, 데이터와 같은 두 훅을 대체할 수 있는 값들을 반환해줬기 때문입니다.
다시 레파지토리 샘플로 돌아가서, /pages/index.js에 있는 useQuery 함수를 사용해서 API로부터 todo list를 가져온 걸 확인할 수 있습니다.
const { status, data, error, isFetching } = useQuery('todos', async () => {
const { data } = await axios.get('/api/data')
return data
})
여기서 차이점은 쿼리가 현재 데이터를 다시 가져오는 중인지 아닌지를 알 수 있는 isFetching을 사용했다는 것입니다. 이게 왜 중요한지는 잠시 후에 알게 될 것입니다. 지금은 일단, 원격 데이터를 수정할 수 있는 방법에 대해 알아봅시다.
useMutation hook
useMutation 훅은 일반적으로 원격 데이터를 생성/수정/삭제하는 데 사용됩니다. 이 함수에 데이터를 업데이트 하는 비동기 함수를 인자로 전달하면, mutate 함수를 반환합니다.
const [mutate] = useMutation(
text => axios.post('/api/data', { text }),
)
mutate("Learn about React Query")
또한 mutate 함수가 onSuccess와 onError와 같은 특정 결과를 반환할 때에만 트리거 되는 선택적 함수를 넣을 수도 있습니다. 샘플 레파티토리에선, <form>이 제출되었을 때 API에 새로운 데이터를 POST 하는 mutate 함수가 사용된 것을 확인할 수 있습니다. 또한 POST 요청이 성공하면 <Input>을 빈 칸으로 돌리도록 했습니다.
const [mutatePostTodo] = useMutation(
text => axios.post('/api/data', { text }),
{
onSuccess: () => {
// Query Invalidations
// queryCache.invalidateQueries('todos')
setText('')
},
}
)
그런데 데모에서 새 텍스트를 삽입하려고 하면, todo 리스트가 갱신되지 않는 걸 확인할 수 있습니다. React Query에서 todo 리스트를 다시 가져오기 위해선, setText 함수 바로 위에 있는 주석을 해제해야 합니다.
{
onSuccess: () => {
// Query Invalidations
queryCache.invalidateQueries('todos')
setText('')
},
}
queryCache.invalidateQueries는 todo 키를 사용해서 캐시를 무효화하고, React Query가 데이터를 다시 가져오도록 합니다.
queryCache 유틸리티
데모의 DevTools에서 봤듯이, React Query는 todo라는 키로 가져온 데이터를 캐시하지만, staleTime을 설정하지 않으면 가져온 데이터는 자동적으로 오래된 데이터가 됩니다.
queryCache는 쿼리를 더욱 잘 다룰 수 있게 해주는 많은 함수를 가진 유틸리티 인스턴스입니다. 샘플의 queryCache.invalidateQueries 함수는 React Query가 todo 리스트에 새 요청을 보내도록 하고 있습니다. 아래 링크는 queryCache에서 사용 가능한 메서드의 전체 목록입니다.
(https://tanstack.com/query/v4/?from=reactQueryV3&original=https://react-query-v3.tanstack.com/#querycache)
결론
React Query는 원격 데이터를 전역 상태에 넣을 필요가 없도록 해주고, 데이터 요청을 관리하기 위한 훌륭한 훅 라이브러리입니다. 단지 데이터를 어디에서 가져올지 라이브러리에게 알려주기만 하면, 별도의 코드나 설정 없이 캐싱, 백그라운드 업데이트, 오래된 데이터를 처리할 수 있습니다.
또한 React Query는 React Query 로직의 몇 줄만 추가하면 useState와 useEffect를 대체할 수 있습니다. 이는 장기적으로, 애플리케이션의 유지보수성과 반응성, 그리고 빠른 속도를 유지하는 데 도움을 줄 것입니다.
만약 더 자세히 알아보고 싶다면 React Query 도큐먼트를 확인해보세요.
(https://tanstack.com/query/v4/?from=reactQueryV3&original=https://react-query-v3.tanstack.com)