공식문서 : https://tanstack.com/query/latest/docs/react/quick-start
참고자료 : https://tech.kakaopay.com/post/react-query-1/
필요성
프로젝트를 진행하면서 apis 폴더에 모든 서버 통신 함수들을 관리하다보니, 같은 동작임에도 불필요하게 반복되는 부분이 많았다.
axios는 서버 통신을 할 때, fetch에 비해 json.stringfy()와 같은 필수적이지만 중복되는 동작들을 처리해주고 error, data, loading에 따른 동작을 비동기적으로 처리해주는 강력한 도구이다.
그러나 캐싱과 같은 서버 상태를 다루려면 종전의 fetch와 마찬가지로 필수적이지만 중복되는 코드들이 많아진다.
이 문제는 사실 redux와 같은 전역 상태 라이브러리를 같이 활용하여 데이터를 받아오고, 전역으로 관리되는 상태에 적용하여 React 컴포넌트 자체를 렌더링시키면 서버 통신이 이뤄질 때 리렌더링 되므로 어느정도 해결할 수 있다고 하지만,
이렇게 되면 너무나 잦은 리렌더링이 발생하고 이를 제어하고자 한다면 또 다른 조건문을 활용하여 다양한 컴포넌트들을 제어해야 하기 때문에 서버 상태에 따른 컴포넌트 관리가 더욱 복잡해지게 된다.
React-Query는 axios와 같은 비동기 함수들을 서버 통신 함수로 사용하면서, 캐싱과 같은 서버 상태에 따른 제어와 예외 처리를 효과적으로 지원하는 라이브러리이다. 이러한 이유로 React-query를 도입하여 프로젝트를 리팩토링해보고, 이미 Custom-hook처럼 사용되고 있는 React-query의 문법을 활용하여 중복되는 코드들을 제어해보고자 한다.
React-Query의 3가지 핵심 컨셉
This code snippet very briefly illustrates the 3 core concepts of React Query:
Queries : 받다
Mutations : 보내다
Query Invalidation : 캐시 무효화
// Query : 데이터 get 요청
// 공식문서 예제
function Todos() {
const { isLoading, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: 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>
)
}
// Mutation : 데이터 수정 요청
function App() {
const mutation = useMutation({
mutationFn: (newTodo) => {
return axios.post('/todos', newTodo)
},
})
return (
<div>
{mutation.isLoading ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: 'Do Laundry' })
}}
>
Create Todo
</button>
</>
)}
</div>
)
}
get 요청 예시
역시 처음 서버 통신 연습으로 가장 만만한 것은 get 요청이다.
데이터는 OpenWeatherApi를 사용했으며, 데이터 로딩 / 에러 / fetch 성공 => 데이터 모두 console로 확인하고자 했다.
// src/apis/Openweather.ts
import axios from 'axios';
import { useQuery } from '@tanstack/react-query';
const weatherRequestApi = import.meta.env.VITE_REACT_APP_OPENWEATHER;
export const getWeather = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { data, isLoading, error } = useQuery(['weather'], () => {
return axios.get(weatherRequestApi)
})
if (isLoading) console.log('Loading...')
if (error) console.log('Error')
return data
}
// src/components/Reactquery.tsx
import { getWeather } from "../apis/openweather";
const ReactQueryPractice = () => {
const handleClick = () => {
const data = getWeather();
console.log(data);
};
return (
<>
<button onClick={handleClick}>리액트 쿼리 연습</button>
</>
);
};
export default ReactQueryPractice;
처음에는 App.tsx에 위의 컴포넌트를 연결해서 data를 확인하려고 했는데.. 동작을 안했다.
왜 문제일까 생각하다가 문득 "useQuery"라는 단어에 꽃혀서 찾아보니,,,
그렇다.. 모든 React-Hook은 컴포넌트의 생명주기에 따라 생성/소멸되는 함수이다.
그래서 다음과 같이 코드를 수정했다.
+ 이왕 수정하는 김에 Loading 상태를 활용해서 UX와 데이터 병목 현상 방지를 미리 연습할 겸 setTimeout 함수도 추가했다.
// src/apis/Openweather.ts
import axios from 'axios';
const weatherRequestApi = import.meta.env.VITE_REACT_APP_OPENWEATHER;
export const getWeather = () => {
setTimeout(() => {
return axios.get(weatherRequestApi);
}, 1000);
}
// src/components/Reactquery.tsx
import { useQuery } from "@tanstack/react-query";
import { getWeather } from "../apis/openweather";
const ReactQueryPractice = () => {
const { data, isLoading, isError, error } = useQuery(["weather"], getWeather);
const handleClick = () => {
if (isError) console.log(error);
if (isLoading) console.log("Loading...");
if (data) console.log(data);
};
return (
<>
<button onClick={handleClick}>리액트 쿼리 연습</button>
</>
);
};
export default ReactQueryPractice;
위의 코드를 실행시켜보니 이제 "동작은 한다"
그러나 2가지 에러 메세지가 뜨고 있다.
첫번째는 받아온 data의 타입이 never로 표시되어 있다. 이는 바닥 타입이라, 더 이상의 조작이 불가능하다.
const data: never
두번째는 콘솔 창에 아래의 에러 메세지가 뜬다
Error: ["weather"] data is undefined
역시.. Query-Key 개념과 타입스크립트를 신경쓰지 않고 일단 작성해보면서 학습해나가자고 했는데.. 그 부분에서 바로 에러가 떴다.
이후의 내용은, 다음 포스팅에서..!
'프레임워크&라이브러리 > React-Query' 카테고리의 다른 글
[리팩토링] 내 정보 조회 : useEffect => useQuery (0) | 2023.07.13 |
---|