클라이언트 상태 vs 서버 상태의 차이
- 클라이언트 상태 (Client State)
- 컴포넌트 내부 혹은 클라이언트에서만 유효한 상태
- 클라이언트가 페이지를 새로고침하면 유실됨.
- 주로
useState,useReducer또는Context API같은 방법으로 관리.
→ 사용자가 입력한 폼 데이터, 모달의 열림/닫힘 상태, 필터링 조건 등.
→ 따라서 특정 컴포넌트나 페이지 안에서만 의미가 있음
const [modalOpen, setModalOpen] = useState(false); const toggleModal = () => setModalOpen(!modalOpen);
- 서버 상태 (Server State)
- 서버에 저장된 데이터로 외부 API로부터 가져오는 정보
- 서버 상태는 비동기적으로 가져와야 하고, 네트워크 지연, 실패 등의 에러 처리가 필요함
- 여러 컴포넌트에서 동일한 서버 상태를 공유할 때 복잡도가 커질 수 있음
- 주로 React Query, SWR 같은 라이브러리를 사용해 관리
→ 사용자의 예약 정보, 결제 상태, 호텔 방의 가용 여부 등.
const fetchUser = async (id) => { const res = await fetch(`/api/user/${id}`); return res.json(); };
React Query의 장점과 사용 이유
1. 캐싱으로 중복 API 호출 방지
- 동일한 API 요청을 반복할 필요 없이 데이터를 캐시하여 사용
→ 예약 상태 페이지와 결제 페이지에서 동일한 예약 정보를 조회할 때 중복 호출 방지
useQuery('reservation', fetchReservation, { staleTime: 1000 * 60 * 5 });
useQuery: React Query에서 데이터를 요청하고 관리하기 위한 훅.
주어진 쿼리 키에 따라 데이터를 가져오고, 캐싱 및 업데이트를 처리
- 첫 번째 인자
'reservation': 쿼리의 고유한 키. 이 키는 캐시에 저장된 데이터를 식별하는 데 사용. 동일한 키를 가진 쿼리가 있을 경우, React Query는 이전에 가져온 데이터를 재사용.
- 두 번째 인자
fetchReservation: 데이터를 가져오기 위한 함수. 이 함수는 실제 API 요청을 수행하고, 데이터를 반환해야 함. 일반적으로 비동기 함수로 작성되어 있어야 함.
- 세 번째 인자
{ staleTime: 1000 * 60 * 5 }: 쿼리의 옵션을 설정하는 객체. staleTime: 이 옵션은 데이터를 "신선한" 상태로 간주하는 시간을 밀리초 단위로 설정.- 이 예에서는 5분(1000ms * 60초 * 5분)으로 설정되어 있음.
- 즉, 이 시간 동안은 같은 쿼리가 다시 요청되지 않고 캐시된 데이터를 사용.
- 5분이 지나면 데이터가 "구식"으로 간주되어, 다음 쿼리 요청 시 데이터를 재요청 함.
2. 자동 리페칭 (Refetching)
- 데이터가 변경되었을 가능성이 있는 시점에 자동으로 데이터를 리페칭합니다.
- 예: 창을 다시 열거나 포커스할 때 최신 상태를 자동으로 가져옵니다.
useQuery('reservation', fetchReservation, { refetchOnWindowFocus: true });
- 세 번째 인자
{ refetchOnWindowFocus: true }: 쿼리의 옵션을 설정하는 객체. refetchOnWindowFocus: 이 옵션이true로 설정되어 있으면, 사용자가 브라우저 탭을 다시 활성화할 때마다 해당 쿼리가 자동으로 재요청됨.- 즉, 사용자가 다른 탭에서 돌아왔을 때 최신 데이터를 보장하기 위해 데이터를 다시 가져오는 기능.
사용자가 브라우저에서 탭을 전환한 후 다시 돌아왔을 때 예약 데이터를 항상 최신 상태로 유지하도록 보장하는 방식으로 작동
3. 데이터의 일관성 유지
- 여러 컴포넌트에서 동일한 데이터를 사용하는 경우, 캐시를 통해 상태를 동기화
- 데이터가 업데이트되면 자동으로 관련된 컴포넌트가 갱신됨
const queryClient = useQueryClient(); const mutation = useMutation(newReservation, { onSuccess: () => queryClient.invalidateQueries('reservations'), });
const queryClient = useQueryClient();:useQueryClient는 React Query의 쿼리 클라이언트를 가져오는 훅.- 쿼리 클라이언트는 쿼리와 뮤테이션의 상태를 관리하고, 데이터를 캐시하고, 쿼리를 무효화하는 등의 작업을 수행함.
const mutation = useMutation(newReservation, { ... }):useMutation은 데이터를 변경하는 작업(예: 생성, 업데이트, 삭제)을 수행하기 위한 훅.- 이 훅은 주어진 함수(여기서는
newReservation)를 사용하여 뮤테이션을 수행 newReservation은 예약을 생성하기 위한 비동기 함수로, API 요청을 통해 새로운 예약을 생성
onSuccess: () => queryClient.invalidateQueries('reservations'):onSuccess는 뮤테이션이 성공적으로 완료된 후 호출되는 콜백 함수.- 예약이 성공적으로 생성된 후, 쿼리 클라이언트를 사용하여
'reservations'라는 키를 가진 쿼리를 무효화 함 invalidateQueries: 이 메소드는 지정된 쿼리를 무효화하여, 다음 번 요청에서 최신 데이터를 가져오도록 강제함. 즉, 새로운 예약이 추가된 후, 예약 목록을 다시 요청하여 최신 상태를 반영하도록 함
4. 에러와 로딩 상태 자동 관리
- React Query는 API 호출에 필요한 로딩, 성공, 실패 상태를 자동으로 관리해줌
const { isLoading, error, data } = useQuery('user', fetchUser); if (isLoading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; return <div>User: {data.name}</div>;
const { isLoading, error, data } = useQuery('user', fetchUser);:useQuery훅을 호출하여'user'라는 쿼리 키를 사용해 사용자 정보를 요청.fetchUser는 사용자 데이터를 가져오는 비동기 함수.- 반환된 객체에서
isLoading,error,data를 구조 분해 할당하여 각각의 상태를 관리.
if (isLoading) return <p>Loading...</p>;:- 데이터가 로딩 중인 상태일 때 "Loading..."이라는 메시지를 화면에 표시
if (error) return <p>Error: {error.message}</p>;:- 데이터 요청 중 오류가 발생했을 때 해당 오류 메시지를 화면에 표시
return <div>User: {data.name}</div>;:- 데이터 로딩이 완료되고 오류가 발생하지 않았을 경우, 사용자 정보를 화면에 표시함.
5. 서버 상태와 클라이언트 상태의 구분을 명확하게 해줌
- 서버에서 가져온 데이터를 컴포넌트 상태로 직접 관리하려면 복잡한 로직이 필요함
- React Query는 서버 상태를 별도로 관리하며, 클라이언트 상태와 책임 범위를 명확히 분리함.
6. Mutations을 통한 서버 데이터 변경 관리
- 데이터를 생성, 수정, 삭제하는 등의 서버 상태 변경 작업을 쉽게 수행합니다.
- 성공적으로 변경된 이후에는 관련된 데이터를 자동으로 갱신합니다.
const mutation = useMutation(updateUser, { onSuccess: () => queryClient.invalidateQueries('user'), }); mutation.mutate({ id: 1, name: 'Updated User' });
- 비동기 로직을 단순화:
useEffect와 같은 복잡한 코드가 필요하지 않음.
- 일관된 데이터 상태 관리: 여러 컴포넌트에서 데이터를 쉽게 공유하고 유지할 수 있음.
- 성능 최적화: 캐싱과 배치 업데이트로 성능을 개선
- 생산성 향상: 에러 처리, 로딩 상태 등을 자동 관리하므로 개발 시간이 단축됨