React Query 사용해보기

Lee·2022년 6월 4일
2
post-thumbnail

React Query를 사용하는 이유

React Query를 사용하기전에 Redux, Redux Toolkit, Redux Saga를 이용했다.

PostSaga.ts

function getPostAPI(lastItemId) {
  return axios.get(`${BASE_URL}/posts`, {
    params: { take: 5, lastItemId },
  });
}

function* getPostSaga() {
  try {
    const lastItemId = yield select(({ post }) => post.posts.lastItemId);
    const {
      data: { posts, lastId },
    } = yield call(getPostAPI, lastItemId);
    yield put(getPostSuccess({ posts, lastId, targetField: "posts" }));
  } catch (error) {
    yield put(getPostError);
    console.log(error);
    alert(error.response.data.message);
  }
}
//이런식의 패턴이 반복됨

PostSlice.ts

const postSlice = createSlice({
  name: "post",
  initialState: {
    loading: false,
    createCommentLoading: null,
    patchPostLoading: null,
    createPostLoading: false,
    posts: { data: [], hasMore: true, lastItemId: null },
    myPosts: { data: [], hasMore: true, lastItemId: null },
    modalVisibleValue: false,
  },
  reducers: {
    getPostRequest(state) {
      state.loading = true;
    },
    getPostSuccess(state, { payload }) {
      state.loading = false;
      const target = state[payload.targetField];
      if (!target.lastItemId || state.modalVisibleValue) {
        target.data = payload.posts;
      } else {
        target.data = [...target.data, ...payload.posts];
      }
      target.lastItemId = payload.posts[payload.posts.length - 1].id;
      target.hasMore = target.lastItemId !== payload.lastId;
    },
    getPostError(state) {
      state.loading = false;
    },
 //이런식의 패턴 반복

이런식의 패턴이 계속 반복되면서 서버와 통신하는데, 나중에 보면 store에 클라이언트 데이터와 서버 데이터가 공존된다. 또한 코드를 쭉 읽어보면 이 코드들이 상태관리를 위한 코드인지 아니면 API통신을 위한 코드인지 의문이 든다.

그리고

PostSilce.ts

  initialState: {
    loading: false,
    createCommentLoading: null,
    patchPostLoading: null,
    createPostLoading: false,
    posts: { data: [], hasMore: true, lastItemId: null },
    myPosts: { data: [], hasMore: true, lastItemId: null },
    modalVisibleValue: false,
  },

에서 서버에 요청을 보내면 loadingtrue... 요청이 끝나면 false로 바뀌는 형태가 계속 반복이 된다.

또한 posts,myPost안에 data안에 서버로부터 온 게시물 data들이 저장되어 있다. 또한 새로운 글을 작성하거나 새로운 댓글을 달면 서버로 data를 post를 하고 또 내 store에 값을 직접 넣어줘야한다(새로고침 안하고 새로운 게시물 or 댓글을 바로 보일 수 있게 하기 위해서)

간단한 React Query 사용법

index.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { RecoilRoot } from "recoil";
import { QueryClientProvider, QueryClient } from "react-query";

const root = ReactDOM.createRoot(document.getElementById("root") as Element);

const queryClient = new QueryClient();

root.render(
  <React.StrictMode>
    <RecoilRoot>
      <QueryClientProvider client={queryClient}>
          <App />
      </QueryClientProvider>
    </RecoilRoot>
  </React.StrictMode>
);

우선 Redux를 사용할때

 <Provider store={store}>
 	<App />
 </Provider>

이런식으로 Provider을 사용해서 App를 감싸는것처럼
QueryClientProvider를 사용해서 App를 감싼다.

useQuery

 const {
   data,
   dataUpdatedAt,
   error,
   errorUpdatedAt,
   failureCount,
   isError,
   isFetched,
   isFetchedAfterMount,
   isFetching,
   isIdle,
   isLoading,
   isLoadingError,
   isPlaceholderData,
   isPreviousData,
   isRefetchError,
   isRefetching,
   isStale,
   isSuccess,
   refetch,
   remove,
   status,
 } = useQuery(queryKey, queryFn?, {
   cacheTime,
   enabled,
   initialData,
   initialDataUpdatedAt
   isDataEqual,
   keepPreviousData,
   meta,
   notifyOnChangeProps,
   notifyOnChangePropsExclusions,
   onError,
   onSettled,
   onSuccess,
   placeholderData,
   queryKeyHashFn,
   refetchInterval,
   refetchIntervalInBackground,
   refetchOnMount,
   refetchOnReconnect,
   refetchOnWindowFocus,
   retry,
   retryOnMount,
   retryDelay,
   select,
   staleTime,
   structuralSharing,
   suspense,
   useErrorBoundary,
 })
 
 // or using the object syntax
 
 const result = useQuery({
   queryKey,
   queryFn,
   enabled,
 })

공식 문서를 보면 뭐가 엄청 길다...
서버에 get를 요청할때 사용한다.

간단하게 Options부터 살펴보면

useQuery(queryKey, queryFn?, {....})

queryKey : 첫번째 파라미터이고

string | unknown[]

를 받는다.
꼭 들어가야하는 필수 파라미터이며 useQuery에 부여되는 고유 Key값이다.

queryFn: 두번째 파라미터이고

(context: QueryFunctionContext) => Promise<TData>

필수 파라미터이며 쿼리가 데이터를 요청하는 데 사용할 함수이다.

세번째 파라미터에는 객체가 오는데 필수가 아니고 옵션이다.
몇개만 소개하자면

enabled:(boolean)
기본값은 true이고 false로 바꾸면 query가 비활성화된다.

retry:(boolean | number | (failureCount: number, error: TError) => boolean)
false인 경우 실패한 쿼리는 재시도하지 않는다.
true인 경우 실패한 쿼리는 무한히 재시도한다.
숫자로 설정된 경우에는 실패한 쿼리 수가 해당 숫자를 충족할 때까지 실패한 쿼리를 다시 시도한다.

onSuccess:((data: TData) => void)
쿼리가 성공적으로 새 데이터를 가져오거나 setQueryData를 통해 캐시가 업데이트될 때마다 함수를 실행한다.

onError:((error: TError) => void)
쿼리에 에러가 발생하고 에러가 전달되면 이 함수가 실행된다.

이제는 return값을 살펴보자
몇개만 소개하자면
data:
기본값은 undefined이고 쿼리가 성공적일때 받는 데이터이다.
error:
기본값은 null이고 에러가 발생했을때 쿼리가 던져주는 error객체이다
status:
idle - query가 쉬고 있는 상태, enabled: false일때와 초기 상태일때
loading - fetching중인 상태
error- fetch에 실패한 상태
success- fetch 성공한 상태

useMutation

const {
   data,
   error,
   isError,
   isIdle,
   isLoading,
   isPaused,
   isSuccess,
   mutate,
   mutateAsync,
   reset,
   status,
 } = useMutation(mutationFn, {
   mutationKey,
   onError,
   onMutate,
   onSettled,
   onSuccess,
   retry,
   retryDelay,
   useErrorBoundary,
   meta,
 })
 
 mutate(variables, {
   onError,
   onSettled,
   onSuccess,
 })

useMutation는 useQuery와 다르게 데이터를 create, update, delete 할때 사용한다
아까 useQuery보다는 짧다...
Options부터 살펴보자

useMutation(mutationFn, {....})

mutationFn:
첫번째 파라미터이고 필수로 있어야한다.
onSettled:
필수가 아닌 옵션이며, mutation의 성공했을때의 데이터 또는 실패했을때 error를 받아서 실행시킨다
onSuccess:
필수가 아닌 옵션이며, mutation이 성공 했을때 data를 파라미터로 받고 실생시킨다
onError:
필수가 아닌 옵션이며, mutation이 실패 했을때 error를 파라미터로 받고 실생시킨다

return 값에는
data:
성공했을때 나오는 data값이며, 기본값은 undifined이다
mutate:
안에 보내고 싶은 variables들을 넣어서 mutation을 실행시킬 수 있다. variables들은 mutationFn에 전달되는 객체이다.

useQuery와 useMutation뿐만 아니라 useInfiniteQuery도 있는데 무한 스크롤을 구현할때 유용하게 쓰인다. 좀 더 공부를 해봐야겠다.

profile
프론트엔드 개발자

0개의 댓글