최근 네이버 재직자님의 스터디를 계기로 React Query에 관심이 생겨 개념과 활용법을 정리해보려 한다.
💡 react-query 를 사용하면 서버의 상태를 간단하고 효율적이게 처리 할 수 있습니다.
✅ gcTime (Garbage Collection Time)
캐시된 쿼리 데이터가 모든 컴포넌트에서 언마운트된 후에도 얼마 동안 메모리에 유지될지를 밀리초 단위로 설정합니다.
✅ staleTime
데이터가 신선하다고 간주되는 시간. 이 시간내에는 refetch 를 하지 않는다.
시나리오. 만약 stale time이 2초, gcTime이 10초라면 ?
1. 처음 마운트
2. 2초 내 재사용
3. 2초 이후에도 마운트 중
4. 컴포넌트 언마운트
5. 10초 안에 다시 마운트되면?
6. 10초가 지나면
$ pnpm install ms
$ pnpm install -D @types/ms

추가적으로 gcTime 또한 설정 가능
💡 서버에서 데이터를 보낼 때는 useMuation을, 데이터를 가져올 때는 useQuery를 사용하시면 됩니다.
import { useQuery } from "@tanstack/react-query";
import { getWorkerList } from "../selfApi";
import { useAtomValue } from "jotai";
import { headerParamsAtom } from "@shared/stores/atom";
// 근로자 리스트를 조회할 때 사용하는 파라미터 타입 정의
interface WorkerListParams {
selectedPage: number; // 현재 선택된 페이지
limit: number; // 페이지당 항목 수
contentParams: {
start_date?: string; // 검색 시작일 (선택)
end_date?: string; // 검색 종료일 (선택)
search_text?: string; // 검색어 (선택)
};
}
// 근로자 리스트를 가져오는 커스텀 훅
const useWorkerList = ({
selectedPage,
limit,
contentParams,
}: WorkerListParams) => {
// 전역 상태에서 헤더에 필요한 파라미터 가져오기
const headerParams = useAtomValue(headerParamsAtom);
// 데이터를 페이지 단위로 잘라주는 유틸 함수
function disassemble(index: number, data: [], size: number) {
const res = [];
for (let i = 0; i < data.length; i += size) {
res.push(data.slice(i, i + size)); // size 단위로 잘라서 배열에 push
}
return res[index] || []; // 요청한 페이지에 해당하는 데이터 반환
}
// useQuery를 사용하여 서버에서 데이터 패칭
return useQuery({
queryKey: [useWorkerList.getKey(), selectedPage, limit], // 쿼리 키는 리스트 식별용 + 페이지, 제한 수
queryFn: async () => {
// API에 넘길 모든 파라미터 병합
const params = Object.assign(
{},
contentParams,
headerParams
);
// API 호출
const res = await getWorkerList(params);
// 응답에서 데이터 추출 (rsMap이 없으면 빈 배열)
const tableData = res.data?.rsMap ?? [];
// 분할된 페이지 데이터와 전체 수를 반환
return {
data: disassemble(selectedPage - 1, tableData, limit),
totalCount: tableData.length,
};
},
});
};
// 쿼리 키를 외부에서 재사용할 수 있도록 static 키 제공
useWorkerList.getKey = () => ["workerList"];
export default useWorkerList;
...
// useWorkerList 훅을 호출하여 데이터를 패칭하고 상태를 받아옴
const { data, refetch, isFetching, isError, error } = useWorkerList({
selectParams, // 필터링용 select 박스 값
selectedPage, // 현재 페이지
limit, // 한 페이지당 아이템 수
contentParams, // 검색 조건 및 날짜 범위
});
...
// 콘텐츠 Wrapper 컴포넌트, 검색 필터 및 범위를 지정
<Content
label="" // 화면 타이틀
type="range" // 검색 필터 타입 (기간 검색)
searchText={searchText} // 검색어 입력값
onSearchText={setSearchText} // 검색어 입력 변경 핸들러
selected={searchDate} // 선택된 날짜 범위
onSelect={setSearchDate} // 날짜 변경 핸들러
onSearchEvent={onSearchEvent} // 검색 버튼 클릭 시 실행할 함수
>
{/* 테이블 틀(헤더 포함)을 감싸는 컴포넌트 */}
<Table label="" width={width}>
{/* 고차 컴포넌트: 로딩, 에러, 정상 데이터를 분기 처리함 */}
<AsyncTableBody
isFetching={isFetching} // 데이터 로딩 중 여부
isError={isError} // 에러 발생 여부
error={error} // 에러 객체
refetch={refetch} // 다시 요청할 수 있는 함수
// 로딩 상태일 때 보여줄 컴포넌트 (콜백 형태로 전달)
loadingFallback={() => (
<Skeleton
width={width} // 셀 너비
size={limit} // 몇 줄 그릴지 (페이지당 항목 수)
menu={menuList.length} // 셀 개수
/>
)}
// 에러 상태일 때 보여줄 컴포넌트 (에러와 refetch 전달 가능)
errorFallback={(err, refetch) => (
<DataFetchError
label={""} // 어떤 화면에서 에러났는지 라벨
error={err} // 에러 내용
refetch={refetch} // 다시 시도할 수 있는 버튼 등에서 사용
/>
)}
>
{/* 실제 데이터를 렌더링하는 테이블 본문 */}
<TableBody
label="" // 타이틀
data={data?.data} // 테이블에 표시할 데이터
width={width} // 셀 너비
filteredIndex={filteredIndex} // NO 표시 시 시작 인덱스 계산값
/>
</AsyncTableBody>
</Table>
{/* 페이지네이션: 페이지 변경 및 limit 변경 가능 */}
<CommonPagination
totalCount={data?.totalCount} // 전체 데이터 개수
selectedPage={selectedPage} // 현재 선택된 페이지
limit={limit} // 페이지당 보여줄 항목 수
onPageChange={(page) => { // 페이지 번호 변경 시
setSelectedPage(page);
refetch(); // 새 페이지 데이터 패칭
}}
onLimitChange={(newLimit) => { // 한 페이지 항목 수 변경 시
setLimit(newLimit);
setSelectedPage(1); // 페이지 초기화
refetch(); // 데이터 다시 요청
}}
/>
</Content>
const queryClient = useQueryClient();
// 예: 쿼리 무효화
queryClient.invalidateQueries(["workerList", selectedPage, limit]);
// 예: 캐시된 데이터 직접 가져오기
const cachedData = queryClient.getQueryData(["workerList", selectedPage, limit]);
// 예: 캐시된 데이터 수동으로 설정
queryClient.setQueryData(["workerList", selectedPage, limit], newData);