오늘은 리액트 쿼리라는 라이브러리를 왜 사용해야 하는지 어떤 기능이 있는지 그리고 리액트 쿼리의 기본 사용법을 알아보도록 하겠습니다.
리액트에서 서버에 데이터를 요청해서 가져오기, 데이터를 서버에 업데이트, 캐싱 작업 등을 하는데 쉽게 처리할 수 있도록 도와주는 라이브러리 입니다.
쉽게 말해서 리액트에서 서버 상태를 관리하는데 도움을 주는 라이브러리 입니다.
리액트 쿼리를 사용함으로써 개발자는 클라이언트 상태와 서버 상태를 명확하게 분리하여 관리하고 처리할 수 있습니다.
useState, useEffect로 상태를 관리하고 비동기 로직을 처리하면 되는 거지 왜 굳이 새로운 라이브러리를 배워서 처리를 해야 하지?라고 궁금해할 수 있습니다.
첫번째로는
리액트를 사용해서 개발하다 보면 아주 많은 상태들을 생성하고 관리하게 됩니다. 클라이언트 상태, 서버 상태 등을 따로 구분하지 않고 애플리케이션에 필요한 상태들을 생성합니다.
리액트 쿼리를 사용하면 서버 상태를 생성하지 않아도 되고 isLoading 같은 로딩 상태인 클라이언트 상태를 따로 만들지 않아도 됩니다. 리액트 쿼리가 로딩, 에러 관련 상태를 제공 해줍니다.
두번째로는 리액트에서 사용자가 애플리케이션에 접속 후 어떤 행동도 하지 않는 경우 서버에 데이터를 요청하거나 업데이트를 하지 않았기 때문에 서버 상태와 현재 애플리케이션에서 사용하는 데이터가 달라서 사용자에게 잘못된 데이터를 보여 줄 수 있습니다. 리액트 쿼리에서는 다양한 기능을 통하여 서버 상태를 받아와 데이터를 갱신하는 기능이 있습니다.
세번째로는 캐싱 기능이 있어 불필요한 서버 요청을 줄일 수 있습니다.
그럼 개발자는 관리해야 할 상태가 줄어들고 자연스럽게 코드도 간단해질 것입니다.
그럼 직접 기존의 리액트에서 서버에 데이터를 요청하는 코드를 보시고 리액트 쿼리에서는 어떻게 서버에 데이터를 요청하는지 확인해 보도록 합시다.
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getData = async () => {
try {
setIsLoading(false);
const res = await axios.get("주소");
setIsLoading(true);
setData(res);
} catch (e) {
console.log(e);
}
};
getData();
}, []);
위의 코드는 리액트에서 서버에 데이터를 요청하는 코드입니다. 그럼 리액트 쿼리를 사용하여 서버에 데이터를 요청하는 코드를 보시겠습니다.
const getData = async () => {
try {
const res = await axios.get("주소");
return res;
} catch (e) {
console.log(e);
}
};
const {isLoading, data} = useQuery(["getData"], getData);
위와 같이 작성할 수 있습니다.
서버에 데이터를 요청하는 비동기 함수인 getData 코드를 작성하고 useQuery 2번째 인자에 전달해 주면 useQuery는 서버에게 요청한 데이터와 로딩, 에러 관련 등의 상태를 반환해 줍니다.
어떠신가요? 코드도 훨씬 간단해진 것을 확인하실 수 있습니다.
물론 이 장점만 있다면 우리는 리액트 쿼리를 배우지 않았을 것입니다.
그럼 지금부터 리액트 쿼리의 다양한 기능을 소개하도록 하겠습니다.
캐싱 : 개발자가 데이터 캐싱 방법을 지정할 수 있습니다. 언제 캐싱 할지 언제 업데이트할지를 정할 수 있습니다.
서버로부터 데이터를 요청, 업데이트할 수 있습니다.
일관된 방법으로 비동기 처리를 할 수 있습니다. 개발자는 리액트 쿼리가 만들어둔 api를 사용하여 비동기 처리를 함으로써 다른 개발자가 코드를 봐도 이해가 쉽고 유지 보수가 쉬어질 수 있습니다.
stale 데이터를 fresh 데이터로 갱신해 줍니다. stale 데이터란 실제 서버 데이터와 클라이언트 데이터가 서로 일치하지 않을 가능성이 존재하는 데이터를 의미합니다. 리액트 쿼리는 사용자가 마우스로 어떤 행동을 하거나 개발자가 지정한 일정 시간이 지난 뒤에는 데이터가 stale 데이터라고 판단하여 다시 서버에 요청하여 fresh 데이터로 갱신하는 기능을 제공 합니다.
5.페이지 네이션, 무한 스크롤을 지원합니다. 해당 기능을 구현하기 위해서 전체 데이터 상태, 현재 화면에 보여주고 있는 상태 등 다양한 상태들을 생성해 구현해야 하는데 리액트 쿼리는 이런 상태들을 관리해 주고 처리해 주는 라이브러리 입니다. 클라이언트에서 필요한 데이터만 서버에서 가져오기 때문에 성능적으로 좋은 이점을 가지게 됩니다.
오류 처리와 네트워크 상태 관리를 자동으로 해주기 때문에 개발자가 따로 처리를 하지 않아도 됩니다.
동일한 데이터에 대한 서버 중복 요청을 쿼리 키를 이용하여 구분하고 하나의 요청으로 통합하여 네트워크 요청을 최소화 합니다.
그럼 이제 리액트 쿼리 api 에 대해서 알아보도록 하겠습니다.
npm i react-query
위의 명령어로 리액트 쿼리를 설치 해주시면 됩니다.
import {QueryClient, QueryClientProvider} from "react-query"
const queryClient = new QueryClient();
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<div>
<Home />
</div>
</QueryClientProvider>
);
};
export default App;
리액트 쿼리를 사용하기 위해서 쿼리, 쿼리 상태를 관리하는 메소드들을 포함하는 객체를 생성해줘야 합니다.
생성 방법은 new QueryClient() 로 생성하시면 됩니다.
그 후 컴포넌트의 최상단 컴포넌트에 QueryClientProvider 컴포넌트를 감싸주고 client 에 생성한 queryClient 객체를 props 로 전달 합니다.
이제 본격적으로 리액트 쿼리에서 제공하는 api 를 알아보겠습니다.
useQuery 는 서버로부터 데이터를 요청하여 받아오는 GET api 입니다. 코드로 사용법을 알아보겠습니다.
const {data} = useQuery(쿼리 키, 쿼리 함수, 옵션);
위와 같이 useQuery 에는 3개의 인자를 전달 할 수 있고 첫번째 자리는 쿼리키를 두번째 자리는 쿼리함수를 세번째 자리는 옵션을 전달 합니다.
맞춤법 검사를 원하는 단어나 문장을 입력해 주세요. 쿼리 키는 유니크한 키가 포함한 배열입니다. 첫 번째 인덱스에는 유니크한 문자열 형태인 키 이름을 작성해야 하며 두 번째 자리부터는 쿼리 함수의 인자로 전달됩니다. 쿼리 함수에 전달되는 인자는 문자열, 객체 등이 가능합니다.
유니크한 쿼리 키는 다른 곳에서도 동일한 쿼리를 불러올 때 유용하게 사용됩니다. 유니크한 쿼리 키는 리액트 쿼리가 쿼리 데이터를 판별하는 데 사용됩니다.
쿼리 키가 같으면 캐싱하고 쿼리 키가 다르면 서버에 데이터를 다시 요청합니다.
useQuery를 통해서 쿼리를 정의할 때 전달하는 함수입니다. 비동기 익명 함수, 정의해둔 함수를 전달할 수 있습니다.
쿼리에 사용할 옵션들을 정의할 수 있습니다.
많은 옵션들이 있지만 자주 사용되는 옵션들을 소개하겠습니다.
enable : useQuery 가 자동으로 서버에 데이터를 요청해서 받아오는 것을 막는 옵션입니다. 기본 값은 true입니다. false로 작성한다면 자동으로 서버에 데이터를 요청하지 않습니다.
staleTime : 리액트 쿼리가 서버에 요청해서 받아온 데이터가 fresh 하다고 생각하는 시간을 말합니다.
기본값은 0이고 숫자 또는 infinity 값을 작성할 수 있습니다. 숫자는 밀리 세컨드이고 예를 들어 1000이라면 리액트 쿼리가 현재 데이터를 fresh 하다고 생각하는 시간은 1초입니다.
infinity는 말 그대로 무한이며 데이터를 항상 fresh 데이터로 판단합니다.
cacheTime : 리액트 쿼리가 데이터를 메모리에 기억하고 있는 시간입니다. 기본 값은 5분입니다. 값으로는 숫자 또는 infinity 가 올 수 있으며 숫자는 밀리 세컨드이고 infinity는 영원히 메모리에 데이터를 저장합니다.
onSucess : 리액트 쿼리가 데이터 요청을 성공하면 호출되는 콜백 함수를 정의합니다. 매개변수로는 서버에 요청한 데이터가 전달됩니다.
onError : 리액트 쿼리가 데이터 요청을 실패하면 호출되는 콜백 함수를 정의합니다. 매개변수로는 요청에 실패와 관련한 error 가 전달됩니다.
onSettled : 리액트 쿼리가 데이터 요청을 실패, 성공 여부와 상관없이 호출되는 콜백 함수를 정의합니다. 인자는 두개가 전달되며 첫번째는 요청 성공해서 받아 온 데이터, 두번째 인자는 요청 실패했을 때의 error 가 전달됩니다.
retry : 리액트 쿼리가 데이터 요청을 실패했을 경우 재요청 유무 또는 횟수에 관련한 옵션입니다.
기본 값은 false이며 데이터 요청은 실패해도 재요청 하지 않습니다.
true는 데이터 요청에 실패하면 지속적으로 재요청을 합니다.
숫자도 들어갈 수 있는데 데이터 요청 시 재요청 횟수를 작성할 수 있습니다.
select : 리액트 쿼리가 서버에 요청하여 받아 온 데이터를 가공할 때 사용되는 콜백 함수입니다. 매개변수로는 서버에서부터 요청하여 응답받은 데이터입니다.
keepPreviousData : 리액트 쿼리가 서버에 데이터를 재요청 했을 때 응답이 오기 전까지 요청 전의 데이터를 유지할지에 대한 옵션입니다. 기본값은 false이며 true 이면 이전 데이터를 새로운 데이터가 응답이 오기 전까지 유지합니다.
그럼 이제 useQuery로 서버에 데이터를 요청하면 어떤 값이 반환되는지 알아보도록 하겠습니다. 일단 객체 형태로 반환되면 일반적으로 개발자가 필요한 값들만 구조분해 할당 문법으로 꺼내 사용합니다.
반환 객체에는 어떤 값들이 들어 있는지 알아보겠습니다.
useQuery는 비동기적으로 작동합니다. 만약 여러 개의 useQuery를 동시에 작동하고 싶다면 useQuries를 사용하면 됩니다.
const res = useQueries({
queries : [
{ queryKey : ["get1"], queryFn : getData1},
{ queryKey : ["get2"], queryFn : getData2}
]
});
위와 같이 useQueries에 객체 형태로 배열에 넣어주면 됩니다.
다른 옵션 값이나 반환 값은 useQuery 와 동일합니다.
useQuery를 동기적으로 사용할 수 있는 방법도 있습니다. 바로 useQuery의 세번째 인자의 옵션에서 enabled에 값을 넣으면 그 값이 true 일 때만 useQuery를 실행합니다.
예를 들어 어떤 비동기 요청을 통해서 bigData를 받아와 해당 데이터를 이용해 서버에 요청해서 smallData를 받아온다고 하면
const {data : bigData} = useQuery(["bigData"], getBigData);
const {data : smallData} = useQuery(["smallData", bigData],()=> getSmallData(bigData));
위와 같이 코드를 작성한다면 리액트 쿼리는 smallData를 서버에 요청해서 가져오는 것에 실패합니다. 그 이유는 서버에 요청할 때 bigData 가 없기 때문이죠 bigData는 서버에서 요청해 받아와야만 사용할 수 있습니다.
이때
const {data : bigData} = useQuery(["bigData"], getBigData);
const {data : smallData} = useQuery(["smallData", bigData],()=> getSmallData(bigData), {
enabled : !!bigData });
위와 같이 smallData를 받아오는 useQuery의 옵션으로 enabled를 작성하여 bigData 가 true 일 때만 useQuery 가 작동하도록 할 수 있습니다.
다음 시간에는 리액트 쿼리에서 서버에 데이터를 삭제하고 업데이트 하는 api 인 useMutaion 에 대해서 알아보도록 하겠습니다.