기존의 client state
를 다루는 상태관리 라이브러리들과 다르게 sever state
를 Fetching, Caching, 비동기적으로 업데이트 하는데 도움을 주는 라이브러리다.
프로젝트를 진행하면서 상태관리에 대해 고민을 하게 되었다.
- 기능추가가 될 수록 store가 커진다.
- API 관련해서 작성되는 코드수와 반복되는 작업이 너무 많다.
- 컴포넌트가 렌더링 하는 작업만 하면 좋을거 같은데 API나 다른 로직들도 포함된다.
- 현재 프론트에서 보여주는 모든 데이터는 API를 통해 가져온 데이터 인데 이 데이터를 다시 state로 만들어서 사용 시 이데이터의 값을 보장할 수 있는지?
위의 고민들을 하고 있을때 기존에 rtk-Query를 사용했을때 비동기 API 처리가 되게 간단했던게 기억이 났고 마침 프리온 보딩을 진행하면서 React-Query에 대해 더 자세히 알 수 있어서 이점을 정리하고 도입하게 되었다.
1. 코드의 라인 수가 줄어 들어서 코드 가독성이 좋아짐
2. 기존의 비동기 API로직을 한곳에서 확인이 가능해서 관심사 분리에 용이하다.
3. onSuccess
onError
isFetching
등 ErrorFlag를 지원해줘서 편리하게 사용이 가능하다.
4. 지원해주는 다양한 옵션을 이용하여 효율적으로 데이터 변환이 가능
select
진짜 짱 편하다.리액트쿼리에서 캐싱 관련 기능이 주된 기능인데 현재 진행하고 있는 프로젝트에서는 비동기 API의 데이터와 동기화가 되어 있어야 해서 캐싱된 값을 사용하지는 못했다.
서버로부터 데이터를 조회할 때 사용한다.
queryKey
를 이용하여 캐싱된 값을 이용하거나 API로 데이터를 가져온다.회사에서
SWR
을 사용중인데 SWR의 경우에는 key 값으로API URL
을 사용한다. 서버에 의존된 서버상태이다 보니까 API URL을 key값으로 사용하게 되니 따로 어떤 값을 key로 사용해야할지, 이값이 이미 사용중인 값인지 아닌지에 대한 고민을 하지 않아도 되어서 관리하기가 매우 편하다는 생각을 하여 후에 어드민 개발 시 React Query의 key 값도 API URL을 이용 하였다.
대표적인 인수로는
- QueryKey, QueryFn, options...
대표적인 반환값으로는
- isError, isSuccess, isLoading, isFetching...
select 옵션을 사용하면 데이터를 가공 시킬 수 있다.
//hooks/queries/useMainAccTimesQuery.ts
export const useMainAccTimesQuery = (options?: UseQueryOptions) => {
return useQuery(["accTimes"], getAccumulationTimes, { select: ({ data }) => data });
};
//utils/decode.ts
export const decodeUserData = (init: UserInfoResponse): Omit<UserInfoType, "isLogin"> => ({
loginID: init.login,
profileImage: init.profileImage,
isAdmin: init.isAdmin,
inoutState: init.inoutState,
});
//hooks/queries/useMainQuery.ts
const { data, ...queryInfo } = useQuery(["mainInfo"], getUserInfo, {
select: (data: AxiosResponse<UserInfoResponse>) => decodeUserData(data.data),
onSuccess: (data) => {
setUserInfo(data);
},
});
//hooks/queries/useMonthTimeLogsQuery.ts
type useMonthTimeLogsProps = {
year: number;
month: number;
options?: UseQueryOptions;
};
export const useMonthTimeLogsQuery = ({ year, month, options }: useMonthTimeLogsProps) => {
...
const { data, ...queryInfo } = useQuery(
["timeLogs", year, month],
() => getLogsmonth(year, month),
...
);
...
};
export const useMainQuery = (options?: UseQueryOptions) => {
const [userInfo, setUserInfo] = useState<UserInfoType>({
loginID: "user",
isAdmin: false,
profileImage: "",
inoutState: "OUT",
});
const { data, ...queryInfo } = useQuery(["mainInfo"], getUserInfo, {
select: (data: AxiosResponse<UserInfoResponse>) => decodeUserData(data.data),
onSuccess: (data) => {
setUserInfo(data);
},
});
return { userInfo, setUserInfo, queryInfo };
무한 스크롤도 useInfiniteQuery 의 hasNextPage,fetchNextPage와 같은 return 값을 이용하여 간단하게 구현가능 하다.
기존에 무한 스크롤 구현시 page도 스테이트로 만들고 hasMore도 state로 만들어 관리했던걸 생각하면 엄청나게 간단하게 구현 가능!!
//hooks/queries/useInfinitiePointCouponList.ts
const { data, refetch, hasNextPage, fetchNextPage } = useInfiniteQuery(
[pointCouponApiUrl.getCouponCodes(campaignId)],
async ({ pageParam = 0 }) => {
return axios.get<PointCouponApi.GetPointCouponList>(pointCouponApiUrl.getCouponCodes(campaignId), {
params: {
offset: pageParam,
pageSize: DEFAULT_PAGE_SIZE,
},
});
},
{
enabled: !isSearch,
getNextPageParam: (lastPage) => {
return lastPage.data.result.length === 0 ? undefined : lastPage.config.params.offset + DEFAULT_PAGE_SIZE;
},
}
);
return {
data:data?.pages
.map((pageData) => {
return [...pageData.data.result];
})
.flat(),
refetch,
}
위의 커스텀 훅을 불러 data를 map 돌면서 사용 가능하다.
데이터가 많아지면서 API를 페이지네이션을 통해 받고, 프론트엔드는 인피니티 스크롤로 표현되게끔 구현했다.
전체 리스트가 아닌 특정 페이지에 대한 API 요청만 보내고 싶다면 useInfiniteQuery의 return값 중 reftch
를 이용하면 아래와 같이 사용할 수 있다.
const { mutate: updateBulkOwnerUserId } = useMutation(
... mutationFunction,
{
onSuccess: (response, variables) =>
refetch({
refetchPage: (page: AxiosResponse<PointCouponApi.GetPointCouponList>) => {
const { result } = page.data;
const shouldRefetch = result.some((coupon) =>
variables.some(({ couponCode }) => couponCode === coupon.couponCode)
);
return shouldRefetch;
},
}),
onError: (error) => {
if (axios.isAxiosError(error)) {
console.error(error);
window.alert(`update bulk coupon code error \n ${error.response?.data.resultMessage}
\n${error.response?.data.resultCode}`);
}
},
}
);
useMutation의 onSuccess 콜백을 통해 useInfiniteQuery의 refetch 메소드를 호출하여 특정 페이지의 데이터만 다시 불러오는 방법
컴포넌트에 API통신을 이용해 데이터를 불러오는 오고 불러온 데이터를 또 state로 관리하는 부분이 섞이다 보니 컴포넌트가 무거워지고 코드가 많아지는 부분을 해결할 수 있어서 좋았다.
공식문서가 진짜 잘되어 있어서 웬만한 부분은 공식문서를 위주로 찾아봤다. 다른 많은 기능들이 있던데 더 찾아봐야 할거 같다.
https://parang.tech/react/2022-react-01/#keep-server-and-client-state-separate
https://parang.tech/react/2022-react-01/
https://tech.kakao.com/2022/06/13/react-query/
https://tech.kakaopay.com/post/react-query-1/
https://tech.osci.kr/2022/07/13/react-query/