RTK-Query 를 사용하면서 React-query 와 비교해보기

D uuu·2024년 4월 9일
0

전역상태관리

목록 보기
1/2

들어가며

프로젝트에서 전역 데이터 관리를 위해 Redux-toolkit 을 사용하기로 결정하고 데이터 fetching 및 관리를 어떻게 할까 고민하다가 rtk-query 라는 걸 알게됐다.
찾아보니 react-query 와 비슷하게 로직을 별도로 작성하지 않아도 fetching, caching, loading, error 등 기능을 제공해주는 라이브러리로 redux-toolkit 을 사용하면 설치없이 사용이 가능했다.

게다가 서버와 통신해서 가져온 데이터를 Redux store 에 저장할 수 있어서 상태 관리도 가능했기에 매우 유용하다는 생각이 들어서 프로젝트에 적용해보기로 했다.

RTK-Query 사용하기

rtk-query 는 base URL 당 하나의 API Slice를 사용한다.
이런식으로 notice 에 해당되는 로직만 작성하면 되니 한번에 관리할 수 있다는 점이 좋았다.

getNoticeList 와 같이 작성하면 알아서 앞 뒤로 use + getNoticeList + query(or mutation) 를 붙여서 훅으로 만들어준다.

export const noticeApi = createApi({
	reducerPath: 'noticeApi',
	baseQuery: fetchBaseQuery({
		baseUrl: `${process.env.REACT_APP_API_URL}/notices`,
	}),
	tagTypes: ['notice'],

	endpoints: (builder) => ({
    // <--! notice list  불러오는 로직 작성 !-->
		getNoticeList: builder.query<
			NoticeType,
			{ pageNumber: number; pageSize: number; category?: NOTICE_CATEGORY_KEYS }
		>({
			query: (arg) => {
				const { pageNumber, pageSize, category } = arg;
				return {
					url: '/',
					params: { pageNumber, pageSize, category },
				};
			},

			providesTags: [{ type: 'notice' }],
		}),
         // <--! notice detail  불러오는 로직 작성 !-->
		getNoticeDetail: builder.query<TNoticeDetail, number>({
			query: (id) => `/${id}`,
		}),
	}),
});

export const { useGetNoticeListQuery, useGetNoticeDetailQuery } = noticeApi;

그럼 아래와 같이 사용하면 된다😎

const { data, isLoading } = useGetNoticeListQuery({
		pageNumber: page,
		pageSize: NOTICE_LIST_PAGE_SIZE,
		category: category,
	});

RTK-Query 에서 Suspense 사용하기

rtk-query 를 사용하면 아래와 같이 isLoading, error 객체를 활용해서 각 상황에 맞는 UI 를 표시할 수 있다.
그러나 매번 api 요청을 할때마다 유사한 코드를 작성해야 하는 번거로움이 있었다.

이러한 반복을 피하기 위해 React 에서 제공하는 SuspenseErrorBoundary 를 활용하여 전역에서 한번에 처리하려고 했다.
그러나 rtk-query 에서는 아직 Suspense 를 지원하지 않고 있었다...😭

function PostsList() {
  const { data, error, isLoading } = useGetPostsQuery();
  
  if(isLoading) return <div>Loading...</div>;

  return (
    <div>
      {error.status} {JSON.stringify(error.data)}
    </div>
  )
}
 	

아래 글을 보면 아직 여러 이슈로 인해 rtk-query에서는 Suspense를 지원하지 않고 있었다.
따라서 Suspense 와 rtk-query 를 함께 사용 하는건 어려울 듯 하여 우선은 Loading 은 위와 같은 방법으로 작성하도록 했다.

https://github.com/reduxjs/redux-toolkit/issues/3517

RTK-Query 에서 전역 에러 처리하기

마찬가지로 rtk-query 에서는 ErrorBoundary 를 함께 사용하지 않는 듯 했다.
그래서 api 에러를 전역에서 처리하기 위해 미들웨어를 활용했다.
리덕스 툴킷에서 미들웨어는 리듀서가 상태를 업데이트하기 전에 액션과 상태를 가로채어 추가적인 작업을 수행할 수 있는데, 주로 애플리케이션의 로깅, 비동기 작업 처리, 라우팅 등과 같은 것들을 처리할 수 있다.

먼저, 하위 컴포넌트에서 발생하는 에러를 감지하고 처리하기 위해 다음과 같이 미들웨어를 작성했다.
rtkQueryErrorHandler 함수는 에러가 발생하면 setError 액션을 dispatch하여 에러 상태를 업데이트한다.

export const rtkQueryErrorHandler: Middleware = (api: MiddlewareAPI) => (next) => (action) => {
	if (isRejected(action)) {
		console.log('error : ', action.payload.originalStatus);
		next(setError(action.payload.originalStatus));
	}

	return next(action);
};

다음으로, 전역적으로 에러 상태를 관리하기 위해 errorSlice를 작성했다.
이 errorSlice 는 단순히 에러 코드를 받아서 저장하고, 정리하는 역할을 한다.

import { createSlice } from '@reduxjs/toolkit';
import { RootState } from './store';

const initialState = {
	status: null,
};

export const errorSlice = createSlice({
	name: 'error',
	initialState,
	reducers: {
		setError: (state, action) => {
			state.status = action.payload;
		},
		clearError: (state) => {
			state.status = null;
		},
	},
});

export const errorStatus = (state: RootState) => state.error;
export const { setError, clearError } = errorSlice.actions;

export default errorSlice.reducer;

마지막으로, 최상위 컴포넌트인 App 컴포넌트에서 에러 상태를 감지하고 처리하도록 했다.
에러가 있으면 ErrorModal을 띄우도록 구현하여, 하위 컴포넌트에서 어디서든 API 에러가 발생하면 모달이 띄워지도록 했다.

function App() {
	const error = useSelector(errorStatus);
	const dispatch = useDispatch();

	const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
	const [errorMessage, setErrorMessage] = useState('');
	const { errorHandler } = useApiError();

	useEffect(() => {
		if (error.status !== null && !isErrorModalOpen) {
			setIsErrorModalOpen(true);
			const message = errorHandler(error.status);
			setErrorMessage(message);
		}
	}, [error]);

	const handleCloseModal = () => {
		setIsErrorModalOpen(false);
		dispatch(clearError());
	};

	return (
		<Container>
			{isErrorModalOpen && <ErrorModal closeModal={handleCloseModal} message={errorMessage} />}
			<Outlet />
		</Container>
	);
}

export default App;

구현한 화면👇

RTK-Query 와 React-Query 비교

rtk-query 를 사용하면서 자연스레 react-query 와 비교를 하게 됐는데,

react-query 는 리액트 기반 라이브러리로 상대적으로 사용이 더 간편하고 로직이 간단하다는 장점이 있다.
Suspense 나 ErrorBoundary 와 같이 리액트 기반 기능들을 지원하기에 함께 사용하는데 어려움이 없었고, 관련 리소스가 많아서 문제 해결에 있어서 문서나 블로그 등을 참고할 수 있어서 적용이 쉬웠다.

반면, rtk-query 는 리덕스 기반으로 설계되어 있기 때문에 리덕스에 대한 지식이 필요하고 상태관리와 데이터 로직을 리덕스만의 표준화된 방식을 따라야 했다. 따라서 react-query 보다는 자유도는 떨어지지만 설계대로 코드를 작성하면 되기 때문에 적용하는데 큰 어려움은 없었다.
다만, react-query 에 비해 리소스가 너무 적어 자료를 참고하는데 어려움을 많이 겪었다.
또한 리액트와 함께 사용할 때 지원하지 않는 기능들이 있어 당황스러운 부분도 있었다. 하지만 상태 관리와 데이터 패칭 및 관리를 한 번에 할 수 있다는 점은 매우 유용했다.

앞으로 프로젝트에서 데이터 패칭 및 관리 라이브러리를 선택할때, 이번 경험을 바탕으로 rtk-query 와 react-query 의 각각의 특징을 고려해서 어느 것이 더 유리할지 선택하는데 많은 도움이 될 것 같다.

profile
배우고 느낀 걸 기록하는 공간

0개의 댓글

관련 채용 정보