React-query 란?
React에서 데이터 관리를 단순화하기 위해 설계된 라이브러리
서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러핸들링 등 비동기 과정을 더욱 편하게 하는데 사용된다.
Redux와 같은 상태 관리 라이브러리를 사용하고 있어도 React-query가 필요한 이유는 store에 서버 데이터와 클라이언트 데이터가 공존, 상호작용하며 서버 데이터도 클라이언트 데이터도 아닌 끔찍한 혼종이 되는 것을 방지할 수 있기 때문이다.
즉, 서버와 클라이언트 데이터를 분리하여 관리할 수 있다.
React-query 장점
1. 자동 데이터 캐싱 기능
말할 것도 없이 최고의 장점이다.
중복 데이터 요청이 발생할 경우 캐싱된 데이터를 반환하여 성능을 향상시키고 트래픽을 감소시킨다.
이때 캐싱 타임 등을 사용자가 직접 설정할 수 있다.
2. API 요청을 위한 규격화된 방식 제공
하라는 대로 쓰면 된다.
3. 에러, 로딩 처리 편리
isError, isLoading 등의 리턴값을 이용하면 컴포넌트에서 굉장히 편하게 각각에 대한 처리를 할 수 있다.
const { data: workbooks, isLoading } = useClassWorkbookListQuery(nowClassId, nowStudyType);
{isLoading ? (
<Loading width={8} height={8} />
) : ( ... )
}
로딩 시간 동안 lottiefiles를 띄워주었다.
4. staleTime, cacheTime 등의 지정을 통해 상태 갱신 주기 제어 가능
쿼리 별로 시간을 달리 지정할 수 있다.
5. React hook과 사용하는 구조가 비슷
러닝커브가 굉장히 낮다. React를 쓸 줄 알면 익히기가 매우 쉽다.
6. 동일한 데이터에 대한 여러 요청을 단일 요청으로 중복 제거 (옵션에 따라 중복 호출 허용 시간 조절 가능)
7. 실패한 쿼리는 자동으로 3번 재시도 (변경 가능)
8. 데이터가 오래 되었다고 판단되면 다시 get ➡ invalidateQueries
9. post / delete 등의 데이터 업데이트 이후 자동으로 get 요청 수행
기존에 데이터를 update하는 API 요청 이후 then 처리에서 get 요청을 따로 수행하는 것을 쿼리문 안쪽으로 옮긴 것이다. 코드도 훨씬 깔끔하고 편하다. 이때 useQuery의 key 값을 사용해서 get 요청 수행을 알린다.
const useClassStudentAdd = () => {
const queryClient = useQueryClient();
return useMutation(fetcher, {
onSuccess: (data) => {
// 학생 추가 후 목록 재조회
return queryClient.invalidateQueries(queryKeys.CLASS_STUDENT_LIST);
},
});
};
10. 무한스크롤 ➡ useInfiniteQuery
이 외에도 더 많은 장점이 있다. 위의 장점은 써보면 더 크게 느껴질 것이다. (이제 React-query 없이 API 어캐 짬;;)
useQuery
get 요청 처리
파라미터
1. unique Key
이 요청이 어떤 API를 호출했는지 구분할 수 있는 식별키
다른 컴포넌트에서도 해당 키를 사용하면 호출 가능
배열로 넘기면,
- 0번 값은 string값으로 다른 컴포넌트에서 부를 값이 들어간다.
- 두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달된다. (해당 값이 바뀔 때만 서버에 query 요청 보내도록 할 수 있음)
2. 비동기 함수(api호출 함수)
promise를 return 하는 함수
3. 옵션
- enabled : 쿼리가 자동으로 실행되지 않게 설정하는 옵션 (동기적으로 사용 가능)
- ex) email 변수가 존재할 때만 쿼리 요청을 하고 싶으면 → enabled: !!email
- staleTime
- cacheTime
- retryDelay
- onSuccess : 성공 시 실행
- onError : 실패 시 실행
- onSettled : 성공 / 실패 모두 실행
- initialData : 캐시된 데이터가 없을 때 표기할 초기값
return
api의 성공, 실패여부, api return 값을 포함한 객체
- data
- isLoading : 저장된 캐시가 없는 상태에서 데이터를 요청 중일 때 (로딩 UI 처리가 쉬움)
- isFetching : 캐시가 있거나 없거나 데이터가 요청 중일 때
✅
▫ isFetching은 캐싱 된 데이터 유무에 상관없이 데이터 Fetching 때마다 true를 리턴
▫ isLoading은 캐싱 된 데이터가 없을 때만 true를 리턴 (initialData 옵션을 설정하면 항상 false를 리턴)
비동기로 작동
➡ 한 컴포넌트에 여러개의 useQuery가 있다면 하나가 끝나고 다음 useQuery가 실행되는 것이 아닌 두개의 useQuery가 동시에 실행된다.
여러개의 비동기 query가 있다면 useQueries 권장
useQueries
쿼리들을 묶어서 처리할 수 있게 해주는 훅
자체적으로 options 값 설정 X → useQueries 안에 선언되는 쿼리는 options 값 설정 가능
사용 예시
import { useQuery } from "react-query";
import * as queryKeys from "@/constants/queryKeys";
import authAxios from "@/utils/customAxios/authAxios";
import { Memo } from "@/types/Memo";
const fetcher = () =>
authAxios.get("/api/memo").then(({ data }) => {
const memo: Memo[] = data.data;
return memo;
});
const useMemoListQuery = () => {
return useQuery([queryKeys.GET_MEMO], fetcher, {
refetchOnWindowFocus: false,
});
};
export default useMemoListQuery;
useMutation
POST, PUT, DELETE
useMutation의 mutate함수는 Promise를 반환하지 않는다. → 직접적으로 결과 할당해서 사용 불가
✅ 함께 사용 가능한 기능
1. queryClient
2. optimistic update : 성공을 예상하며 미리 UI부터 갱신
📌 Optimistic Update
useMutation이 성공할 것이라 가정하고 미리 화면의 UI를 바꾸고 나서, 결과에 따라 확정 / 롤백하는 방식
- mutateAsync 함수는 Promise를 반환
- 성공, 실패와 같은 서버의 응답 결과 처리
- useMutation은 중복 호출에 대한 제어 옵션X
- 쓰로틀링, 디바운싱을 구현해서 사용
- isLoading이나 useIsMutating을 활용해서 제어
옵션
onMutate: (variables)
- mutationFn이 실행되기 전에 먼저 실행
- mutation 함수가 전달받은 파라미터가 동일하게 전달
- optimistic update 사용 시 유용
- 여기서 반환된 값은 onError, onSettled 함수에 전달
onSuccess: (data, variables)
- mutation() 성공 시 실행
- invalidationQueries로 해당 쿼리를 invalidata하고 refetch
- update 후 get 요청 자동으로 진행
- invalidateQueries가 실행되면 해당 query는 무효화되며 refetching 시도
- 즉, 사용자가 새로고침 하지 않아도 데이터 갱신
- refetchActive : 해당 query에 대해 refetching X, 무효화 only
onError
- mutation() 에러 시 실행
onSettled
- mutation()의 성공 / 에러 여부와 상관없이 데이터를 전달받는다.
mutationFn(variables)
- API 요청 함수
- variables : mutate가 전달하는 객체
return
mutate
- mutation을 실행시키는 함수
사용 예시
import authAxios from "@/utils/customAxios/authAxios";
import { useMutation, useQueryClient } from "react-query";
import * as queryKeys from "@/constants/queryKeys";
const fetcher = (memo: string) =>
authAxios.post("/api/memo", { content: memo }).then(({ data }) => data.data);
// 메모 등록 - OK
const useMemoWrite = () => {
const queryClient = useQueryClient();
return useMutation(fetcher, {
onSuccess: (data) => {
return queryClient.invalidateQueries(queryKeys.GET_MEMO);
},
});
};
export default useMemoWrite;
invalidateQueries로 특정 쿼리를 refetch 하기 위해서는 queryKey를 활용해야 한다. (고유한 queryKey가 필요한 이유)
useInfiniteQuery
무한 스크롤 구현 시, 특정 조건 (스크롤이 바닥에 닿는 등) 하에서 다음 데이터 목록을 패치한다.
- 1번째 인자 : key
- 2번째 인자 : 패치함수 → pageParam이라는 페이지 값 지정 (기본값 1로 설정)
- 3번째 인자 : 옵션
- getNextPageParam() 함수 : 다음 API 요청에 사용할 pageParam 값을 정한다. (통상 첫 번째 인자인 lastPage에 nextCursor를 같이 보내주므로 이로 설정)
data는 pageParams(pageParam 정보들), pages(페이지 별 데이터) 2가지 프로퍼티를 가진 객체로 내려온다.
→ 여기서 pages의 data 프로퍼티들만 매핑해서 보여주면 된다.
쿼리 반환값 활용
- hasNextPage : getNextPageParam() 에 따른 다음 페이지 존재 여부 (Boolean)
- fetchNextPage : 다음 데이터를 불러오는 메서드 (data의 pages 배열의 제일 끝에 새로운 데이터를 담음)
- isFetchingNextPage : 다음 데이터를 패치중인지 여부(Boolean)
사용 예시
import { DONATE_SERVER_URL } from "@/utils/urls";
import axios from "axios";
import { useInfiniteQuery, useQuery } from "react-query";
import * as queryKeys from "@/constants/queryKeys";
const fetcher = (pageParam: number) =>
axios
.get(DONATE_SERVER_URL + `/board`, {
params: {
page: pageParam,
},
})
.then(({ data }) => {
return data.data;
});
const useDonateListQuery = () => {
return useInfiniteQuery(
queryKeys.DONATE_LIST,
({ pageParam = 0 }) => fetcher(pageParam),
{
getNextPageParam: (lastPage, pages) => {
return lastPage.last ? undefined : lastPage.number + 1;
},
suspense: true,
},
);
};
export default useDonateListQuery;
'React' 카테고리의 다른 글
[ React ] 캘린더 컴포넌트 직접 만들기 (feat. date-fns) (0) | 2024.06.12 |
---|---|
react-datepicker 라이브러리로 공통 컴포넌트 만들기 (DatePicker, DateRangePicker) (0) | 2024.06.07 |
@react-icons/all-files로 모듈 크기 개선 (0) | 2024.05.21 |
react-icons 라이브러리 활용 (Figma, 프로젝트) (1) | 2024.05.21 |
[ React ] form에서 onChange 하나로 사용하기 (0) | 2024.03.31 |