React Query 이전의 방식, Redux
React Query는 리액트 애플리케이션에서 서버 상태를 불러오고, 캐싱하며, 지속적으로 동기화 및 업데이트 작업을 도와주는 라이브러리다. 즉, 서버와의 통신을 보다 편리하게 해주는 도구라고 할 수 있다.
React Query를 효과적으로 이해하려면, 같은 작업을 React Query 없이 이전에는 어떻게 (얼마나 번거롭게) 작업했는지를 보면 된다.
Redux
React Query 이전에 사람들은 Redux를 주로 사용했다.
Redux는 Global State Management Library, 즉 전역 상태를 관리하도록 도와주는 라이브러리다.
전역 상태는 리액트 컴포넌트의 Life cycle과 관계없이 비동기 데이터가 관리되기 때문에 캐싱과 같은 최적화 작업을 쉽게 수행할 수 있다는 장점이 있다.
Redux가 React Query를 대체할 수 없는 이유?
Redux의 단점으로는 너무 장황한 보일러 플레이트가 대표적으로 언급된다.
하나의 API 요청을 처리하기 위해 여러 개의 Action, Reducer가 필요하며, 이는 곧 API의 개수가 많아질수록 코드의 분량 뿐만 아니라 복잡성이 높아질 우려가 있다.
또한, 서버와의 API 통신은 통신 그 자체도 중요하지만 통신한 데이터를 관리하는 것 또한 중요하다.
그러나 React Query와 달리 Redux는 API 통신 및 비동기 상태 관리를 위한 라이브러리가 아니다.
따라서 비동기 데이터를 관리하기 위한 로직을 개발자가 결정하고 구현해야 한다.
이는 곧 개발자 간의 데이터 관리 방식/방법의 차이를 야기한다. 팀의 구성원이 많아지고 여러 협업 관계가 얽혀있다면 이는 더 복잡해질 수 있다.
React Query 동작 방식
비동기 데이터 및 API 상태를 관리하기 위한 규격화된 방식이 대두되면서 React Query가 등장했다.
React Query는 내부적으로 queryClient를 사용하여 각종 상태를 저장하고 부가 기능을 제공한다.
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
const { isPending, error, data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/TanStack/query').then((res) =>
res.json(),
),
})
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}
TanStack Query 공식 문서 예제 코드
React Query에 대한 오해
개발중인 프로젝트는 다양한 사용자 권한이 있고, 오직 관리자 권한의 경우 지점을 선택해 지점 별 데이터를 불러온다.
따라서 관리자 로그인 시 모든 페이지를 이동할 때마다 지점을 선택하기 위해 지점 목록을 불러와야 한다.
처음에는 지점 목록을 상수 처리하여 Select 태그를 만들었다가 추후에 지점 목록 조회 API가 추가되면서 서버에서 응답받은 데이터로 Select 태그의 option을 만들어주었다.
그러다 문득 든 생각이, 페이지를 이동할 때마다 매번 지점 목록 데이터를 요청하면 서버에 부하가 생기는 게 아닌가 싶었다.
React Query의 캐싱
내 생각이 쓸데없는 걱정이었던 이유는, React Query는 데이터를 로드할 때 내부적으로 캐시를 사용하므로, 컴포넌트가 다시 렌더링 되더라도 데이터를 새로 요청하지 않고 캐싱된 데이터를 반환한다!
따라서 지점 목록 데이터가 변경되지 않으면 다시 요청하지 않으므로 성능에 무리가 되지 않는다는 것이다.
또한 선택 핸들러와 데이터 검색 로직은 useCallback으로 메모이제이션되어 불필요한 재생성을 방지해주고 있었다.
findBranchInfo는 지점 목록이 큰 경우 성능에 영향을 줄 수 있으나, 일반적으로 지점 목록이 적은 데이터이므로 큰 문제가 되지 않았다.
전체 코드는 다음과 같다.
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
import type { IBranchNameResponse } from '@/types';
import { useBranchNameList } from '../fetchHooks';
export interface UseBranchSelectReturns {
branchState: IBranchNameResponse;
branchNameList: IBranchNameResponse[] | undefined;
handleBranchSelect: (event: ChangeEvent<HTMLSelectElement>) => void;
}
/**
* 전체 지점 목록과 선택된 지점의 데이터를 불러오기 위한 커스텀 훅
*
*
*/
export const useBranchSelect = (): UseBranchSelectReturns => {
const { data: branchNameList } = useBranchNameList();
const [branchState, setBranchState] = useState<IBranchNameResponse>({
branchId: 0,
name: '',
code: '',
});
const findBranchInfo = useCallback(
(branchName: string) => {
return branchNameList?.find(({ name }) => name === branchName) || null;
},
[branchNameList],
);
const handleBranchSelect = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => {
const {
target: { value },
} = e;
const selectedBranch = findBranchInfo(value);
if (selectedBranch) {
setBranchState(selectedBranch);
}
},
[branchNameList],
);
useEffect(() => {
if (branchNameList && branchNameList.length > 0) {
setBranchState(branchNameList[0]);
}
}, [branchNameList]);
return {
branchState,
branchNameList,
handleBranchSelect,
};
};
정리
React Query는 비동기 데이터를 다루는 데 있어서 개발자들에게 많은 이점을 제공한다. 특히 캐싱, 데이터 동기화, 그리고 로드 상태 관리 등의 작업을 규격화된 방식으로 처리함으로써 코드의 가독성과 유지보수성을 크게 향상시킨다.
반면 Redux나 단순한 상태 관리 도구를 사용할 때는 비동기 상태 관리 로직을 직접 작성해야 하며, 이는 개발자의 부담을 가중시키고 프로젝트 규모가 커질수록 관리가 복잡해지는 단점이 있었다. React Query는 이러한 문제를 해결하며, 더 나아가 서버 상태를 효율적으로 관리할 수 있는 방법을 제공한다.
위 코드에서 useBranchSelect와 같은 커스텀 훅은 React Query의 장점을 활용하여 데이터를 요청하고 캐싱하며, 사용자가 데이터를 선택하고 관리하는 데 필요한 기능을 직관적으로 제공한다. React Query의 캐싱 메커니즘 덕분에 불필요한 API 호출을 줄이고, 성능 최적화를 자연스럽게 이루는 것을 확인할 수 있었다.
결론적으로, React Query는 서버 상태를 다루는 리액트 애플리케이션에서 매우 강력하고 유용한 도구임을 알 수 있었다.
단순히 데이터를 불러오는 것을 넘어, 데이터의 유효성 검증, 자동 리페치, 캐싱, 그리고 옵션 기반의 유연한 상태 관리를 제공함으로써 개발자가 애플리케이션의 핵심 로직에 집중할 수 있도록 도와준다.
앞으로도 React Query를 적극적으로 활용하여 더욱 효율적이고 가독성 높은 코드를 작성할 수 있도록 노력해야겠다.
'기술 스택 > React' 카테고리의 다른 글
jQuery와 React에 대해서 (feat. Virtual DOM, 가상 돔) (0) | 2025.03.18 |
---|---|
게시글 필터링: 프론트엔드 vs 백엔드, 어느 쪽이 더 효율적일까? (0) | 2025.01.07 |
React 조건부 렌더링과 상태 초기화: 컴포넌트의 생명주기를 이해하다 (1) | 2024.12.17 |
Props Drilling은 반드시 지양해야 하는가? (1) | 2024.11.19 |